[
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.idea\n\n\n\n# built application files\n*.apk\n*.ap_\n \n# files for the dex VM\n*.dex\n \n# Java class files\n*.class\n \n# generated files\nbin/\ngen/\nout/\nbuild/\n \n# Local configuration file (sdk path, etc)\nlocal.properties\n \n# Eclipse project files\n.classpath\n.project\n \n# Proguard folder generated by Eclipse\nproguard/\n \n# Intellij project files\n*.iws\n.idea/\n*.iml\n \n# Gradle directory\nbuild/\n.gradle/\n\n\n# Maven directory\ntarget\n \n# OS\n.DS_Store\n*~\n\nproguard-rules.pro"
  },
  {
    "path": "README.md",
    "content": "GoBelieve Android SDK\n-------------------\n\n##demo各模块说明\napp, group_demo, customer_service_demo是application模块\nasynctcp, imsdk, imkit是library模块\n\n1. app模块测试点对点消息\n    app模块可以输入自己和对方的uid(整型),就可以直接和对方聊天.\n    app模块如果只输入自己的uid，那么会进入会话列表界面\n3. group_demo模块测试群组消息\n    group_demo模块可以输入自己的uid和群组的id(整型),就可以直接测试群组消息\n5. customer_service_demo测试客服消息\n    customer_service_demo模块是以客服人员的身份登录,并接受用户发来的客服消息\n\n##应用集成到客户端\n1. import asynctcp, imsdk, imkit模块到自己的app工程\n2. 在Application的onCreate初始化\n\n        String androidID = Settings.Secure.getString(this.getContentResolver(),\n                Settings.Secure.ANDROID_ID);\n\n        //设置设备唯一标识,用于多点登录时设备校验\n        IMService.getInstance().setDeviceID(androidID);\n\n        //监听网路状态变更\n        IMService.getInstance().registerConnectivityChangeReceiver(getApplicationContext());\n\n        mIMService.setPeerMessageHandler(PeerMessageHandler.getInstance());\n        mIMService.setGroupMessageHandler(GroupMessageHandler.getInstance());\n        mIMService.setCustomerMessageHandler(CustomerMessageHandler.getInstance());\n\n3. 登录成功后设置uid,token\n\n        IMService.getInstance().setToken(token);\n        PeerMessageHandler.getInstance().setUID(uid);\n        GroupMessageHandler.getInstance().setUID(uid);\n\n        SyncKeyHandler handler = new SyncKeyHandler(this.getApplicationContext(), String.format(\"sync_key_%d\", uid));\n        handler.load();\n        IMService.getInstance().setSyncKeyHandler(handler);\n\n4. 打开消息db, 数据库表结构参照demo中的MessageDatabaseHelper源代码\n\n        File p = this.getDir(\"db\", MODE_PRIVATE);\n        File f = new File(p, String.format(\"gobelieve_%d.db\", uid));\n        String path = f.getPath();\n        MessageDatabaseHelper dh = MessageDatabaseHelper.getInstance();\n        dh.open(this.getApplicationContext(), path);\n        SQLiteDatabase db = dh.getDatabase();\n        PeerMessageDB.getInstance().setDb(db);\n        EPeerMessageDB.getInstance().setDb(db);\n        GroupMessageDB.getInstance().setDb(db);\n        CustomerMessageDB.getInstance().setDb(db);\n\n5. 启动IMService接受消息\n\n        IMService.getInstance().start();\n\n6. 添加消息observer，处理相应类型的消息\n\n        //连接状态\n        IMService.getInstance().addObserver(ob);\n\n        //点对点消息\n        IMService.getInstance().addPeerObserver(ob);\n        //群组消息\n        IMService.getInstance().addGroupObserver(ob);\n        //直播的聊天室消息\n        IMService.getInstance().addRoomObserver(ob);\n        //实时消息,用于voip的信令\n        IMService.getInstance().addRTObserver(ob);\n        //系统消息\n        IMService.getInstance().addSystemObserver(ob);    \n\n7. 应用进入后台，断开socket链接\n\n        IMService.getInstance().enterBackground();\n\n8. 应用返回前台,重现链接socket\n \n        IMService.getInstance().enterForeground();\n\n9.  发送点对点消息\n\n        Intent intent = new Intent(this, PeerMessageActivity.class);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        intent.putExtra(\"peer_uid\", peer_uid);\n        intent.putExtra(\"peer_name\", \"\");\n        intent.putExtra(\"current_uid\", uid);\n        startActivity(intent);\n\n10. 发送群组消息\n\n        Intent intent = new Intent(this, GroupMessageActivity.class);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        intent.putExtra(\"group_id\", groupId);\n        intent.putExtra(\"group_name\", \"\");\n        intent.putExtra(\"current_uid\", uid);\n        startActivity(intent);\n\n11. 用户注销\n\n        IMService.getInstance().stop()"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    package=\"io.gobelieve.im.demo\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\n    <uses-permission android:name=\"android.permission.FLASHLIGHT\" />\n    <uses-feature android:name=\"android.hardware.camera\" />\n    <uses-feature android:name=\"android.hardware.camera.autofocus\" />\n\n\n    <!--高德地图所需权限-->\n    <!--用于进行网络定位-->\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"></uses-permission>\n    <!--用于访问GPS定位-->\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"></uses-permission>\n    <!--获取运营商信息，用于支持提供运营商信息相关的接口-->\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"></uses-permission>\n    <!--用于访问wifi网络信息，wifi信息会用于进行网络定位-->\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"></uses-permission>\n    <!--这个权限用于获取wifi的获取权限，wifi信息会用来进行网络定位-->\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"></uses-permission>\n    <!--用于访问网络，网络定位需要上网-->\n    <uses-permission android:name=\"android.permission.INTERNET\"></uses-permission>\n    <!--用于读取手机当前的状态-->\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\"></uses-permission>\n    <!--写入扩展存储，向扩展卡写入数据，用于写入缓存定位数据-->\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"></uses-permission>\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n\n\n\n\n    <application\n        android:name=\".IMDemoApplication\"\n        android:usesCleartextTraffic=\"true\"\n        android:allowBackup=\"true\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\">\n\n\n        <activity\n            android:name=\".LoginActivity\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@style/Horizontal_Slide\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".MessageListActivity\"\n            android:exported=\"true\"\n            android:windowSoftInputMode=\"adjustResize\"\n            android:theme=\"@style/imkit.NoActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n            </intent-filter>\n        </activity>\n\n        <!--高德地图配置-->\n        <meta-data\n            android:name=\"com.amap.api.v2.apikey\"\n            android:value=\"fe4ad96eb93239914540892d3dfb76f7\" />\n\n        <!-- 必需： 应用ID -->\n        <meta-data\n            android:name=\"GOBELIEVE_APPID\"\n            android:value=\"7\" />\n\n        <!-- 必需： 应用KEY -->\n        <meta-data\n            android:name=\"GOBELIEVE_APPKEY\"\n            android:value=\"sVDIlIiDUm7tWPYWhi6kfNbrqui3ez44\" />\n\n\n        <activity\n            android:name=\"com.beetle.bauhinia.PeerMessageActivity\"\n            android:exported=\"true\"\n            android:windowSoftInputMode=\"adjustResize|stateHidden\"\n            android:theme=\"@style/imkit.ActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n            </intent-filter>\n        </activity>\n\n\n\n        <activity\n            android:name=\"com.beetle.bauhinia.GroupMessageActivity\"\n            android:exported=\"true\"\n            android:windowSoftInputMode=\"adjustResize|stateHidden\"\n            android:theme=\"@style/imkit.ActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\"com.beetle.bauhinia.CustomerMessageActivity\"\n            android:exported=\"true\"\n            android:windowSoftInputMode=\"adjustResize|stateHidden\"\n            android:theme=\"@style/imkit.ActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\"com.beetle.bauhinia.activity.PhotoActivity\"\n            android:label=\"照片\"\n            android:theme=\"@style/imkit.ActionBar\">\n        </activity>\n\n        <activity\n            android:name=\"com.beetle.bauhinia.activity.MapActivity\"\n            android:label=\"位置\"\n            android:theme=\"@style/imkit.ActionBar\">\n        </activity>\n        <activity\n            android:name=\"com.beetle.bauhinia.activity.LocationPickerActivity\"\n            android:label=\"位置\"\n            android:theme=\"@style/imkit.ActionBar\">\n        </activity>\n\n        <activity android:name=\"com.beetle.bauhinia.activity.CameraActivity\"\n            android:theme=\"@style/imkit.NoActionBar\"/>\n\n        <activity\n            android:name=\"com.beetle.bauhinia.activity.WebActivity\"\n            android:label=\"\"\n            android:theme=\"@style/imkit.ActionBar\">\n        </activity>\n\n        <activity\n            android:name=\"com.beetle.bauhinia.gallery.ui.GalleryGridUI\"\n            android:label=\"@string/gallery_chat_files\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@style/imkit.ActionBar\">\n        </activity>\n\n        <activity\n            android:name=\"com.beetle.bauhinia.gallery.ui.GalleryUI\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@style/imkit.NoActionBar\">\n        </activity>\n\n\n        <activity android:name=\"com.beetle.bauhinia.activity.OverlayActivity\"\n            android:theme=\"@style/imkit.NoActionBar\">\n        </activity>\n\n        <activity android:name=\"com.beetle.bauhinia.activity.MessageFileActivity\"\n            android:label=\"文件预览\"\n            android:theme=\"@style/imkit.ActionBar\">\n        </activity>\n        <activity android:name=\"com.beetle.bauhinia.activity.PlayerActivity\"\n            android:theme=\"@style/imkit.NoActionBar\">\n        </activity>\n\n\n        <!-- authorities 当前包名+'.fileprovider' -->\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"io.gobelieve.im.demo.fileprovider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/file_paths\" />\n        </provider>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n    buildToolsVersion rootProject.ext.buildToolsVersion\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1020\n        versionName \"1.0.2\"\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n    sourceSets {\n        main {\n            manifest.srcFile 'AndroidManifest.xml'\n            java.srcDirs = ['src']\n            res.srcDirs = ['res']\n            aidl.srcDirs = ['src']\n            jniLibs.srcDirs = ['libs']\n            assets.srcDirs = ['src/main/assets', 'assets/']\n        }\n    }\n\n    buildTypes {\n        release {\n\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    packagingOptions {\n        exclude 'META-INF/DEPENDENCIES.txt'\n        exclude 'META-INF/LICENSE.txt'\n        exclude 'META-INF/NOTICE.txt'\n        exclude 'META-INF/NOTICE'\n        exclude 'META-INF/LICENSE'\n        exclude 'META-INF/DEPENDENCIES'\n        exclude 'META-INF/notice.txt'\n        exclude 'META-INF/license.txt'\n        exclude 'META-INF/dependencies.txt'\n        exclude 'META-INF/LGPL2.1'\n    }\n\n    lintOptions {\n        checkReleaseBuilds false\n        abortOnError false\n    }\n}\n\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n\n    implementation project(':imkit')\n    implementation project(':imlib')\n    implementation project(':imsdk')\n    implementation project(':asynctcp')\n\n    implementation 'androidx.legacy:legacy-support-v4:1.0.0'\n    implementation 'androidx.appcompat:appcompat:1.0.0'\n    implementation 'com.netflix.rxjava:rxjava-core:0.20.7'\n    implementation 'com.netflix.rxjava:rxjava-android:0.20.7'\n    implementation 'com.squareup.picasso:picasso:2.71828'\n}\n\n\n\n"
  },
  {
    "path": "app/res/anim/fade_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n    android:duration=\"300\" />"
  },
  {
    "path": "app/res/anim/fade_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:fromAlpha=\"1.0\" android:toAlpha=\"0.0\"\n    android:duration=\"300\" />"
  },
  {
    "path": "app/res/anim/head_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 左上角扩大-->\n  <scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"0.001\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"0.001\"   \n        android:toYScale=\"1.0\"   \n        android:pivotX=\"15%\"  \n        android:pivotY=\"25%\"  \n        android:duration=\"200\" />  \n   "
  },
  {
    "path": "app/res/anim/head_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 左上角缩小 -->\n  <scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"0.001\"   \n        android:fromYScale=\"1.0\"   \n        android:toYScale=\"0.001\"   \n        android:pivotX=\"15%\"  \n        android:pivotY=\"25%\"  \n        android:duration=\"200\" />  \n   "
  },
  {
    "path": "app/res/anim/hold.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?> \n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:interpolator=\"@android:anim/accelerate_interpolator\"\n       android:fromXDelta=\"0\" android:toXDelta=\"0\"\n       android:duration=\"300\" />"
  },
  {
    "path": "app/res/anim/push_bottom_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"50%p\"\n        android:toYDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "app/res/anim/push_bottom_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    \n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"50%p\" />\n\n    \n\n</set>"
  },
  {
    "path": "app/res/anim/push_top_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"-50%p\"\n        android:toYDelta=\"0\" />   \n</set>"
  },
  {
    "path": "app/res/anim/push_top_in2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"0\"   \n        android:toYScale=\"1.0\"   \n        android:pivotX=\"0\"  \n        android:pivotY=\"10%\"  \n        android:duration=\"200\" /> "
  },
  {
    "path": "app/res/anim/push_top_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"-50%p\" />   \n</set>"
  },
  {
    "path": "app/res/anim/push_top_out2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"1.0\"   \n        android:toYScale=\"0\"   \n        android:pivotX=\"0\"  \n        android:pivotY=\"10%\"  \n        android:duration=\"200\" /> "
  },
  {
    "path": "app/res/anim/slide_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"-100%p\"\n        android:toXDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "app/res/anim/slide_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"100%p\"\n        android:toXDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "app/res/anim/slide_out_to_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"0\"\n        android:toXDelta=\"-100%p\" />\n\n</set>"
  },
  {
    "path": "app/res/anim/slide_out_to_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"0\"\n        android:toXDelta=\"100%p\" />\n\n</set>"
  },
  {
    "path": "app/res/drawable/btn_login_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/btn_login_pressed\" android:state_pressed=\"true\" />\n    <item android:drawable=\"@drawable/btn_login_normal\" />\n\n\n</selector>\n"
  },
  {
    "path": "app/res/layout/activity_conversation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/support_toolbar\"\n        android:layout_height=\"wrap_content\"\n        android:layout_width=\"match_parent\"\n        android:layout_gravity=\"left\"\n        android:background=\"@color/theme_primary\"\n        />\n\n    <ListView\n            android:id=\"@+id/list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dip\"\n            android:layout_weight=\"1\" >\n    </ListView>\n</LinearLayout>"
  },
  {
    "path": "app/res/layout/activity_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_background\">\n\n    <!--发送用户id-->\n    <View\n        android:id=\"@+id/line_sender_underline\"\n        style=\"@style/login_edittext_underline\"\n        android:layout_marginTop=\"248dp\" />\n\n    <ImageView\n        android:id=\"@+id/iv_sender\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_above=\"@id/line_sender_underline\"\n        android:layout_alignBottom=\"@id/line_sender_underline\"\n        android:layout_marginBottom=\"9dp\"\n        android:layout_marginLeft=\"22dp\"\n        android:src=\"@drawable/ic_account\" />\n\n    <EditText\n        android:id=\"@+id/et_username\"\n        style=\"@style/login_edittext\"\n        android:layout_above=\"@id/line_sender_underline\"\n        android:layout_alignBottom=\"@id/line_sender_underline\"\n        android:layout_toRightOf=\"@id/iv_sender\"\n        android:hint=\"@string/login_account\" />\n\n    <!--接收用户id-->\n\n    <View\n        android:id=\"@+id/line_receiver_underline\"\n        style=\"@style/login_edittext_underline\"\n        android:layout_below=\"@id/line_sender_underline\"\n        android:layout_marginTop=\"44dp\" />\n\n\n    <ImageView\n        android:id=\"@+id/iv_receiver\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignBottom=\"@id/line_receiver_underline\"\n        android:layout_marginBottom=\"9dp\"\n        android:layout_marginLeft=\"22dp\"\n        android:src=\"@drawable/ic_account\" />\n\n    <EditText\n        android:id=\"@+id/et_target_username\"\n        style=\"@style/login_edittext\"\n        android:layout_alignBottom=\"@id/line_receiver_underline\"\n        android:layout_toRightOf=\"@id/iv_receiver\"\n        android:hint=\"@string/login_target_account\" />\n\n    <Button\n        android:id=\"@+id/btn_login\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/line_receiver_underline\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginRight=\"15dp\"\n        android:layout_marginTop=\"45dp\"\n        android:background=\"@drawable/btn_login_selector\"\n        android:text=\"@string/login_login\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"@dimen/h2\" />\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "app/res/layout/conversation_message.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"80dp\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:paddingLeft=\"8dp\"\n    android:paddingRight=\"8dp\">\n\n\n    <FrameLayout\n        android:layout_width=\"64dp\"\n        android:layout_height=\"64dp\">\n\n        <ImageView\n            android:id=\"@+id/header\"\n            android:layout_width=\"56dp\"\n            android:layout_height=\"56dp\"\n            android:layout_gravity=\"center\"\n            android:src=\"@drawable/avatar_contact\" />\n\n        <TextView\n            android:id=\"@+id/unReadCount\"\n            style=\"@style/DemoMainNewMassage\"\n            android:layout_gravity=\"top|right\" />\n    </FrameLayout>\n\n\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:orientation=\"vertical\">\n\n\n        <TextView\n            android:id=\"@+id/name\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"5dp\"\n            android:textIsSelectable=\"false\"\n            android:textSize=\"16dp\"\n            android:textStyle=\"bold\" />\n\n        <TextView\n            android:id=\"@+id/content\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"5dp\"\n            android:textIsSelectable=\"false\" />\n\n    </LinearLayout>\n</LinearLayout>\n\n\n"
  },
  {
    "path": "app/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"common_bg\">#fcfcfc</color>\n    <color name=\"common_header_blue\">#00abf1</color>\n    <color name=\"view_focused\">#11000000</color>\n    <color name=\"view_pressed\">@color/view_focused</color>\n    <color name=\"btn_login_normal\">#2dafa3</color>\n    <color name=\"btn_login_pressed\">@color/common_header_blue</color>\n    <color name=\"btn_gray_normal\">#c0c0c0</color>\n    <color name=\"bottom_bar_normal_bg\">#2D2F31</color>\n    <color name=\"common_botton_bar_blue\">#2ea7e0</color>\n    <color name=\"common_bottom_bar_normal_bg\">#2d2f31</color>\n    <color name=\"common_bottom_bar_selected_bg\">#161718</color>\n    <color name=\"divider_list\">#cccccc</color>\n    <color name=\"blue_337aba\">#337aba</color>\n    <color name=\"setting_item_pressed\">@color/btn_login_pressed</color>\n    <color name=\"setting_divider\">#e6e6e6</color>\n    <color name=\"gray_33\">#333333</color>\n    <color name=\"bg_chat\">#f2f0eb</color>\n    <color name=\"gray_normal\">#666667</color>\n\n    <color name=\"white_trans_40\">#66FFFFFF</color>\n\n    <color name=\"main_regist\">#fffff0</color>\n\n    <color name=\"theme_primary\">#18b000</color>\n\n\n</resources>\n"
  },
  {
    "path": "app/res/values/dimen_font.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- 一般不用h1和h6, header标题h2, catalog标题h3, tab名称h4, 图标文字h5 -->\n    <dimen name=\"large\">26sp</dimen>\n    <dimen name=\"h0\">24sp</dimen>\n    <dimen name=\"h1\">22sp</dimen>\n    <dimen name=\"h2\">20sp</dimen>\n    <dimen name=\"h3\">18sp</dimen>\n    <dimen name=\"h4\">16sp</dimen>\n    <dimen name=\"h5\">14sp</dimen>\n    <dimen name=\"h6\">12sp</dimen>\n    <dimen name=\"h7\">10sp</dimen>\n</resources>"
  },
  {
    "path": "app/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n    <dimen name=\"header_common_height\">46dp</dimen>\n    <dimen name=\"header_button_width\">48dp</dimen>\n\n    <!--setting-->\n    <dimen name=\"setting_left_padding\">20dp</dimen>\n    <dimen name=\"setting_right_padding\">20dp</dimen>\n    <dimen name=\"setting_item_height\">47dp</dimen>\n    <dimen name=\"setting_item_left_padding\">27dp</dimen>\n    <dimen name=\"setting_item_right_padding\">27dp</dimen>\n    <dimen name=\"setting_item_checkbox_right_padding\">32dp</dimen>\n    <dimen name=\"setting_checkbox_right_margin\">14dp</dimen>\n\n\n    <dimen name=\"margin_chat_activity\">5dp</dimen>\n    <dimen name=\"size_avatar\">50dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"app_name\">IMDemo</string>\n    <string name=\"hello_world\">Hello world!</string>\n    <string name=\"action_settings\">Settings</string>\n    <string name=\"login_account\">发送用户id</string>\n    <string name=\"login_target_account\">接收用户id</string>\n    <string name=\"login_login\">登  录</string>\n    <string name=\"main_settings\">设置</string>\n    <string name=\"main_contacts\">通讯录</string>\n    <string name=\"setting_notification\">接收新消息通知</string>\n    <string name=\"setting_sound\">声音</string>\n    <string name=\"setting_vibrate\">震动</string>\n    <string name=\"setting_logout\">注销</string>\n    <string name=\"chat_send\">发 送</string>\n    <string name=\"text_ack_msg\">已读</string>\n    <string name=\"text_delivered_msg\">送达</string>\n    <string name=\"text_failure\">失败</string>\n    <string name=\"text_sended_msg\">已发送</string>\n    <string name=\"chat_activity_header\">%1$d -> %2$d</string>\n\n</resources>\n"
  },
  {
    "path": "app/res/values/styles.xml",
    "content": "<resources xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!--login-->\n    <style name=\"login_edittext_underline\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">1dp</item>\n        <item name=\"android:background\">@color/white_trans_40</item>\n        <item name=\"android:layout_marginLeft\">15dp</item>\n        <item name=\"android:layout_marginRight\">15dp</item>\n    </style>\n\n    <style name=\"login_edittext\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">45dp</item>\n        <item name=\"android:background\">@null</item>\n        <item name=\"android:layout_marginLeft\">20dp</item>\n        <item name=\"android:layout_marginRight\">15dp</item>\n        <item name=\"android:textColorHint\">@color/white_trans_40</item>\n        <item name=\"android:textColor\">@android:color/white</item>\n        <item name=\"android:textSize\">@dimen/h2</item>\n        <item name=\"android:numeric\">integer</item>\n        <item name=\"android:singleLine\">true</item>\n\n    </style>\n\n\n    <style name=\"AnimFade2\" parent=\"@android:style/Animation.Activity\">\n        <item name=\"android:activityOpenEnterAnimation\">@anim/slide_in_from_right</item>\n        <item name=\"android:activityOpenExitAnimation\">@anim/slide_out_to_left</item>\n        <item name=\"android:activityCloseExitAnimation\">@anim/slide_out_to_right</item>\n        <item name=\"android:activityCloseEnterAnimation\">@anim/slide_in_from_left</item>\n    </style>\n\n\n    <style name=\"Horizontal_Slide\" parent=\"android:Theme.NoTitleBar\">\n        <item name=\"android:windowAnimationStyle\">@style/AnimFade2</item>\n    </style>\n\n    <style name=\"DemoMainNewMassage\">\n        <item name=\"android:background\">@drawable/rc_unread_count_bg</item>\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textColor\">@color/main_regist</item>\n        <item name=\"android:textSize\">10sp</item>\n        <item name=\"android:layout_width\">18dp</item>\n        <item name=\"android:layout_height\">18dp</item>\n        <!-- <item name=\"android:visibility\">gone</item>-->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/io/gobelieve/im/demo/BaseActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.os.Bundle;\nimport androidx.fragment.app.FragmentActivity;\n\nimport com.beetle.im.IMService;\n\nimport java.util.List;\n\n/**\n * BaseActivity\n * Description: 基础Activity\n */\npublic abstract class BaseActivity extends FragmentActivity {\n    @Override\n    protected final void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        onBaseCreate(savedInstanceState);\n        initView(savedInstanceState);\n    }\n\n    /**\n     * 必须在此设置一个ContentView，除非它没有界面\n     *\n     * @param savedInstanceState\n     */\n    protected abstract void onBaseCreate(Bundle savedInstanceState);\n\n    /**\n     * 视图初始化\n     * <p/>\n     * 处理手势绑定、view和fragment的注入\n     *\n     * @param savedInstanceState\n     */\n    protected abstract void initView(Bundle savedInstanceState);\n\n\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n\n        if (!isAppOnForeground()) {\n            //app 进入后台,停止IMService,采用push机制接收离线消息\n            IMService.getInstance().enterBackground();\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        IMService.getInstance().enterForeground();\n    }\n\n    /**\n     * 程序是否在前台运行\n     *\n     * @return\n     */\n    public boolean isAppOnForeground() {\n        // Returns a list of application processes that are running on the\n        // device\n\n        ActivityManager activityManager =\n            (ActivityManager) getApplicationContext().getSystemService(\n                Context.ACTIVITY_SERVICE);\n        String packageName = getApplicationContext().getPackageName();\n\n        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager\n            .getRunningAppProcesses();\n        if (appProcesses == null)\n            return false;\n\n        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {\n            // The name of the process that this object is associated with.\n            if (appProcess.processName.equals(packageName)\n                && appProcess.importance\n                == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/io/gobelieve/im/demo/ConversationView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.squareup.picasso.Picasso;\n\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\n\nimport io.gobelieve.im.demo.model.Conversation;\n\npublic class ConversationView extends FrameLayout implements PropertyChangeListener {\n    protected Context context;\n    protected LayoutInflater inflater;\n    private Conversation conversation = null;\n\n    public ConversationView(Context context) {\n        super(context);\n        this.context = context;\n        this.inflater = LayoutInflater.from(context);\n        inflater.inflate(R.layout.conversation_message, this);\n    }\n\n    public void setConversation(Conversation c) {\n        if (this.conversation != null) {\n            this.conversation.removePropertyChangeListener(this);\n        }\n        this.conversation = c;\n        this.conversation.addPropertyChangeListener(this);\n\n\n        TextView tv = (TextView) this.findViewById(R.id.name);\n        tv.setText(c.getName());\n\n        tv = (TextView)this.findViewById(R.id.content);\n        tv.setText(c.getDetail());\n\n        int placeholder;\n        if (c.type == Conversation.CONVERSATION_PEER) {\n            placeholder = R.drawable.avatar_contact;\n        } else {\n            placeholder = R.drawable.avatar_group;\n        }\n\n        String avatar = null;\n        if (!TextUtils.isEmpty(c.getAvatar())) {\n            avatar = c.getAvatar();\n        }\n\n        ImageView imageView = (ImageView) this.findViewById(R.id.header);\n        Picasso.get()\n                .load(avatar)\n                .placeholder(placeholder)\n                .into(imageView);\n\n        setUnreadCount();\n    }\n\n\n    private void setUnreadCount() {\n        TextView tv = (TextView) this.findViewById(R.id.unReadCount);\n        if (conversation.getUnreadCount() > 0) {\n            tv.setVisibility(VISIBLE);\n            tv.setText(String.valueOf(conversation.getUnreadCount()));\n        } else {\n            tv.setVisibility(GONE);\n        }\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event){\n        if (event.getPropertyName().equals(\"detail\")) {\n            TextView tv = (TextView)this.findViewById(R.id.content);\n            tv.setText(this.conversation.getDetail());\n        } else if (event.getPropertyName().equals(\"name\")) {\n            TextView tv = (TextView) this.findViewById(R.id.name);\n            tv.setText(this.conversation.getName());\n        } else if (event.getPropertyName().equals(\"avatar\")) {\n            int placeholder;\n            if (this.conversation.type == Conversation.CONVERSATION_PEER) {\n                placeholder = R.drawable.avatar_contact;\n            } else {\n                placeholder = R.drawable.avatar_group;\n            }\n\n            String avatar = null;\n            if (!TextUtils.isEmpty(this.conversation.getAvatar())) {\n                avatar = this.conversation.getAvatar();\n            }\n\n            ImageView imageView = (ImageView) this.findViewById(R.id.header);\n            Picasso.get()\n                    .load(avatar)\n                    .placeholder(placeholder)\n                    .into(imageView);\n        } else if (event.getPropertyName().equals(\"unreadCount\")) {\n            setUnreadCount();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/io/gobelieve/im/demo/IMDemoApplication.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.app.Application;\nimport android.os.AsyncTask;\nimport android.os.Environment;\nimport android.os.HandlerThread;\nimport android.provider.Settings;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.beetle.bauhinia.api.IMHttpAPI;\nimport com.beetle.bauhinia.handler.CustomerMessageHandler;\nimport com.beetle.bauhinia.handler.GroupMessageHandler;\nimport com.beetle.bauhinia.handler.PeerMessageHandler;\nimport com.beetle.bauhinia.tools.FileCache;\nimport com.beetle.im.IMService;\nimport com.beetle.bauhinia.toolbar.emoticon.EmoticonManager;\n\nimport java.io.File;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\n\n/**\n * IMDemoApplication\n * Description:\n */\npublic class IMDemoApplication extends Application {\n    private static final String TAG = \"gobelieve\";\n\n    private static Application sApplication;\n\n    private HandlerThread imThread;//处理im消息的线程\n\n    private String mDeviceToken;\n    public String getDeviceToken() {\n        return mDeviceToken;\n    }\n\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        sApplication = this;\n\n        imThread = new HandlerThread(\"im_service\");\n        imThread.start();\n\n        IMService mIMService = IMService.getInstance();\n        //app可以单独部署服务器，给予第三方应用更多的灵活性\n        mIMService.setHost(\"imnode2.gobelieve.io\");\n        IMHttpAPI.setAPIURL(\"https://api.gobelieve.io/v2\");\n\n        String androidID = Settings.Secure.getString(this.getContentResolver(),\n                Settings.Secure.ANDROID_ID);\n\n        //设置设备唯一标识,用于多点登录时设备校验\n        mIMService.setDeviceID(androidID);\n\n        mIMService.setLooper(imThread.getLooper());\n        //监听网路状态变更\n        IMService.getInstance().registerConnectivityChangeReceiver(getApplicationContext());\n\n        //可以在登录成功后，设置每个用户不同的消息存储目录\n        FileCache fc = FileCache.getInstance();\n        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {\n            //can write external storage\n            String path = getExternalFilesDir(null).getAbsolutePath();\n            File dir1 = new File(path, \"download\");\n            if (!dir1.exists()) {\n                dir1.mkdirs();\n            }\n            fc.setDir(dir1);\n            Log.i(TAG, \"file cache:\" + dir1.getAbsolutePath());\n        } else {\n            File f = new File(getFilesDir(), \"cache\");\n            if (!f.exists()) {\n                f.mkdir();\n            }\n            fc.setDir(f);\n            Log.i(TAG, \"file cache:\" + this.getDir(\"cache\", MODE_PRIVATE).getAbsolutePath());\n        }\n\n        mIMService.setPeerMessageHandler(PeerMessageHandler.getInstance());\n        mIMService.setGroupMessageHandler(GroupMessageHandler.getInstance());\n        mIMService.setCustomerMessageHandler(CustomerMessageHandler.getInstance());\n\n        //表情资源初始化\n        EmoticonManager.getInstance().init(this);\n    }\n\n    public static Application getApplication() {\n        return sApplication;\n    }\n}\n"
  },
  {
    "path": "app/src/io/gobelieve/im/demo/LoginActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.app.ProgressDialog;\nimport android.content.Intent;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.provider.Settings;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.Toast;\n\nimport com.beetle.bauhinia.CustomerMessageActivity;\nimport com.beetle.bauhinia.GroupMessageActivity;\nimport com.beetle.bauhinia.PeerMessageActivity;\nimport com.beetle.bauhinia.api.IMHttpAPI;\nimport com.beetle.bauhinia.api.body.PostDeviceToken;\nimport com.beetle.bauhinia.db.CustomerMessageDB;\nimport com.beetle.bauhinia.db.GroupMessageDB;\nimport com.beetle.bauhinia.db.EPeerMessageDB;\nimport com.beetle.bauhinia.db.PeerMessageDB;\nimport com.beetle.bauhinia.handler.GroupMessageHandler;\nimport com.beetle.bauhinia.handler.PeerMessageHandler;\nimport com.beetle.bauhinia.handler.CustomerMessageHandler;\nimport com.beetle.bauhinia.handler.SyncKeyHandler;\nimport com.beetle.im.IMService;\n\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport io.gobelieve.im.demo.model.ConversationDB;\nimport io.gobelieve.im.demo.model.MessageDatabaseHelper;\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.functions.Action1;\n\n/**\n * LoginActivity\n * Description: 登录页面,给用户指定消息发送方Id\n */\npublic class LoginActivity extends BaseActivity implements View.OnClickListener {\n    private final String TAG = \"demo\";\n\n    private static final long APP_ID = 7;\n\n    private static final boolean TEST_PEER = true;\n    private static final boolean TEST_GROUP = false;\n    private static final boolean TEST_CUSTOMER = false;\n\n    private EditText mEtAccount;\n    private EditText mEtTargetAccount;\n\n    AsyncTask mLoginTask;\n    @Override\n    protected void onBaseCreate(Bundle savedInstanceState) {\n        setContentView(R.layout.activity_login);\n    }\n\n    @Override\n    protected void initView(Bundle savedInstanceState) {\n        Button btnLogin = (Button) findViewById(R.id.btn_login);\n        mEtAccount = (EditText) findViewById(R.id.et_username);\n        mEtTargetAccount = (EditText) findViewById(R.id.et_target_username);\n        if (TEST_PEER) {\n            mEtTargetAccount.setHint(\"接受用户id\");\n        } else if (TEST_GROUP) {\n            mEtTargetAccount.setHint(\"群组id(15)\");\n        } else if (TEST_CUSTOMER) {\n            mEtTargetAccount.setHint(\"商店id(7)\");\n        }\n        btnLogin.setOnClickListener(this);\n    }\n\n    void openDB(long currentUID) {\n        File p = this.getDir(\"db\", MODE_PRIVATE);\n        File f = new File(p, String.format(\"gobelieve_%d.db\", currentUID));\n        String path = f.getPath();\n        MessageDatabaseHelper dh = MessageDatabaseHelper.getInstance();\n        dh.open(this.getApplicationContext(), path);\n        SQLiteDatabase db = dh.getDatabase();\n        Log.i(TAG, \"db version:\" + db.getVersion());\n        PeerMessageDB.getInstance().setDb(db);\n        EPeerMessageDB.getInstance().setDb(db);\n        GroupMessageDB.getInstance().setDb(db);\n        CustomerMessageDB.getInstance().setDb(db);\n        ConversationDB.getInstance().setDb(db);\n    }\n\n    private void go2Chat(long sender, long receiver, String token) {\n        IMService.getInstance().stop();\n        PeerMessageDB.getInstance().setDb(null);\n        EPeerMessageDB.getInstance().setDb(null);\n        GroupMessageDB.getInstance().setDb(null);\n        CustomerMessageDB.getInstance().setDb(null);\n        ConversationDB.getInstance().setDb(null);\n\n\n        openDB(sender);\n\n        PeerMessageHandler.getInstance().setUID(sender);\n        GroupMessageHandler.getInstance().setUID(sender);\n        CustomerMessageHandler.getInstance().setUID(sender);\n        CustomerMessageHandler.getInstance().setAppId(APP_ID);\n        IMHttpAPI.setToken(token);\n        IMService.getInstance().setToken(token);\n\n        SyncKeyHandler handler = new SyncKeyHandler(this.getApplicationContext(), \"sync_key\");\n        handler.load();\n\n        HashMap<Long, Long> groupSyncKeys = handler.getSuperGroupSyncKeys();\n        IMService.getInstance().clearSuperGroupSyncKeys();\n        for (Map.Entry<Long, Long> e : groupSyncKeys.entrySet()) {\n            IMService.getInstance().addSuperGroupSyncKey(e.getKey(), e.getValue());\n            Log.i(TAG, \"group id:\" + e.getKey() + \"sync key:\" + e.getValue());\n        }\n        IMService.getInstance().setSyncKey(handler.getSyncKey());\n        Log.i(TAG, \"sync key:\" + handler.getSyncKey());\n        IMService.getInstance().setSyncKeyHandler(handler);\n\n\n\n        IMService.getInstance().start();\n\n        IMDemoApplication app = (IMDemoApplication)getApplication();\n        String deviceToken = app.getDeviceToken();\n        if (token != null && deviceToken != null && deviceToken.length() > 0) {\n            PostDeviceToken tokenBody = new PostDeviceToken();\n            tokenBody.xgDeviceToken = deviceToken;\n            IMHttpAPI.Singleton().bindDeviceToken(tokenBody)\n                    .observeOn(AndroidSchedulers.mainThread())\n                    .subscribe(new Action1<Object>() {\n                        @Override\n                        public void call(Object obj) {\n                            Log.i(\"im\", \"bind success\");\n                        }\n                    }, new Action1<Throwable>() {\n                        @Override\n                        public void call(Throwable throwable) {\n                            Log.i(\"im\", \"bind fail\");\n                        }\n                    });\n        }\n\n        if (TEST_PEER) {\n            Intent intent = new Intent(this, PeerMessageActivity.class);\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            intent.putExtra(\"peer_uid\", receiver);\n            intent.putExtra(\"peer_name\", \"测试\");\n            intent.putExtra(\"current_uid\", sender);\n            startActivity(intent);\n        } else if (TEST_GROUP) {\n            Intent intent = new Intent(this, GroupMessageActivity.class);\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            intent.putExtra(\"group_id\", receiver);\n            intent.putExtra(\"group_name\", \"测试群\");\n            intent.putExtra(\"current_uid\", sender);\n            startActivity(intent);\n        } else if (TEST_CUSTOMER) {\n            Intent intent = new Intent(this, CustomerMessageActivity.class);\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            intent.putExtra(\"store_id\", receiver);\n            intent.putExtra(\"app_id\", APP_ID);\n            intent.putExtra(\"current_uid\", sender);\n            intent.putExtra(\"peer_app_id\", 0L);\n            intent.putExtra(\"peer_uid\", 0L);\n            intent.putExtra(\"peer_app_name\", \"\");\n            intent.putExtra(\"peer_name\", \"\");\n            intent.putExtra(\"store_name\", \"测试商店\");\n            intent.putExtra(\"app_name\", \"demo\");\n            intent.putExtra(\"name\", \"测试用户\");\n            startActivity(intent);\n        }\n        finish();\n    }\n\n\n\n    @Override\n    public void onClick(View v) {\n        if (v.getId() == R.id.btn_login) {\n            if (mEtAccount.getText().toString().length() <= 0) {\n                Toast.makeText(this, \"请设置您的用户id\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n            final long senderId = Long.parseLong(mEtAccount.getText().toString());\n            if (senderId <= 0) {\n                Toast.makeText(this, \"用户id不能为0或者-1\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            long receiver = 0;\n            if (mEtTargetAccount.getText().toString().length() > 0) {\n                receiver = Long.parseLong(mEtTargetAccount.getText().toString());\n                if (receiver <= 0) {\n                    if (TEST_PEER) {\n                        Toast.makeText(this, \"输入接受者id\", Toast.LENGTH_SHORT).show();\n                    } else if (TEST_GROUP) {\n                        Toast.makeText(this, \"输入群组id\", Toast.LENGTH_SHORT).show();\n                    } else if (TEST_CUSTOMER) {\n                        Toast.makeText(this, \"输入商店id\", Toast.LENGTH_SHORT).show();\n                    }\n                    return;\n                }\n            }\n\n            final long receiverId = receiver;\n\n            if (mLoginTask != null) {\n                return;\n            }\n\n            final ProgressDialog dialog = ProgressDialog.show(this, null, \"登录中...\");\n\n            mLoginTask = new AsyncTask<Void, Integer, String>() {\n                @Override\n                protected String doInBackground(Void... urls) {\n                    return LoginActivity.this.login(senderId);\n                }\n                @Override\n                protected void onPostExecute(String result) {\n                    dialog.dismiss();\n                    mLoginTask = null;\n                    if (result != null && result.length() > 0) {\n                        //设置用户id,进入MainActivity\n                        go2Chat(senderId, receiverId, result);\n                    } else {\n                        Toast.makeText(LoginActivity.this, \"登陆失败\", Toast.LENGTH_SHORT).show();\n                    }\n                }\n            }.execute();\n        }\n    }\n\n    private String login(long uid) {\n        //调用app自身的登陆接口获取im服务必须的access token,之后可将token保存在本地供下次直接登录IM服务\n        String api_url = \"http://demo.gobelieve.io\";\n\n        String uri = String.format(\"%s/auth/token\", api_url);\n        try {\n            URL url = new URL(uri);\n            HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n            connection.setRequestMethod(\"POST\");\n            connection.setRequestProperty(\"Content-Type\", \"application/json; utf-8\");\n            connection.setDoOutput(true);\n            connection.setUseCaches(false);\n\n            JSONObject json = new JSONObject();\n            json.put(\"uid\", uid);\n            int PLATFORM_ANDROID = 2;\n            String androidID = Settings.Secure.getString(this.getContentResolver(),\n                    Settings.Secure.ANDROID_ID);\n            json.put(\"platform_id\", PLATFORM_ANDROID);\n            json.put(\"device_id\", androidID);\n            String data = json.toString();\n\n            try(OutputStream os = connection.getOutputStream()){\n                byte[] input = data.getBytes(\"utf-8\");\n                os.write(input, 0, input.length);\n            }\n            int responseCode = connection.getResponseCode();\n            if (responseCode != 200){\n                System.out.println(\"login failure code is:\" + responseCode);\n                return null;\n            }\n            int len = connection.getHeaderFieldInt(\"Content-Length\", 64*1024);\n            byte[] buf = new byte[len];\n            InputStream inStream = connection.getInputStream();\n\n            int pos = 0;\n            while (pos < len) {\n                int n = inStream.read(buf, pos, len - pos);\n                if (n == -1) {\n                    break;\n                }\n                pos += n;\n            }\n            inStream.close();\n            if (pos != len) {\n                return null;\n            }\n            String txt = new String(buf, \"UTF-8\");\n            JSONObject jsonObject = new JSONObject(txt);\n            String accessToken = jsonObject.getString(\"token\");\n            return accessToken;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/io/gobelieve/im/demo/MessageListActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.content.Intent;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.*;\nimport androidx.appcompat.widget.Toolbar;\n\nimport com.beetle.bauhinia.CustomerMessageActivity;\nimport com.beetle.bauhinia.PeerMessageActivity;\nimport com.beetle.bauhinia.db.CustomerMessageDB;\nimport com.beetle.bauhinia.db.EPeerMessageDB;\nimport com.beetle.bauhinia.db.GroupMessageDB;\nimport com.beetle.bauhinia.db.ICustomerMessage;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.PeerMessageDB;\nimport com.beetle.bauhinia.db.message.Audio;\nimport com.beetle.bauhinia.db.message.File;\nimport com.beetle.bauhinia.db.message.GroupNotification;\nimport com.beetle.bauhinia.db.message.GroupVOIP;\nimport com.beetle.bauhinia.db.message.Image;\nimport com.beetle.bauhinia.db.message.Location;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.P2PSession;\nimport com.beetle.bauhinia.db.message.Revoke;\nimport com.beetle.bauhinia.db.message.Secret;\nimport com.beetle.bauhinia.db.message.Text;\nimport com.beetle.bauhinia.db.message.VOIP;\nimport com.beetle.bauhinia.db.message.Video;\nimport com.beetle.im.GroupMessageObserver;\nimport com.beetle.im.IMMessage;\nimport com.beetle.im.IMService;\nimport com.beetle.im.IMServiceObserver;\nimport com.beetle.bauhinia.activity.BaseActivity;\n\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\nimport com.beetle.im.PeerMessageObserver;\nimport com.beetle.im.SystemMessageObserver;\n\nimport io.gobelieve.im.demo.model.Conversation;\nimport io.gobelieve.im.demo.model.ConversationDB;\n\n\npublic class MessageListActivity extends BaseActivity implements IMServiceObserver,\n        PeerMessageObserver,\n        GroupMessageObserver,\n        SystemMessageObserver,\n        AdapterView.OnItemClickListener {\n    private static final String TAG = \"beetle\";\n\n    private List<Conversation> conversations;\n    private ListView lv;\n    protected long currentUID = 0;\n\n\n    private static final long APPID = 7;\n    private static final long KEFU_ID = 55;\n\n\n    private BaseAdapter adapter;\n    class ConversationAdapter extends BaseAdapter {\n        @Override\n        public int getCount() {\n            return conversations.size();\n        }\n        @Override\n        public Object getItem(int position) {\n            return conversations.get(position);\n        }\n        @Override\n        public long getItemId(int position) {\n            return position;\n        }\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n            ConversationView view = null;\n            if (convertView == null) {\n                view = new ConversationView(MessageListActivity.this);\n            } else {\n                view = (ConversationView)convertView;\n            }\n            Conversation c = conversations.get(position);\n            view.setConversation(c);;\n            return view;\n        }\n    }\n\n    // 初始化组件\n    private void initWidget() {\n        Toolbar toolbar = (Toolbar)findViewById(R.id.support_toolbar);\n        setSupportActionBar(toolbar);\n\n        lv = (ListView) findViewById(R.id.list);\n        adapter = new ConversationAdapter();\n        lv.setAdapter(adapter);\n        lv.setOnItemClickListener(this);\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        Log.i(TAG, \"main activity create...\");\n\n        setContentView(R.layout.activity_conversation);\n\n        Intent intent = getIntent();\n\n        currentUID = intent.getLongExtra(\"current_uid\", 0);\n        if (currentUID == 0) {\n            Log.e(TAG, \"current uid is 0\");\n            return;\n        }\n\n        IMService im =  IMService.getInstance();\n        im.addObserver(this);\n        im.addPeerObserver(this);\n        im.addGroupObserver(this);\n        im.addSystemObserver(this);\n\n        loadConversations();\n        initWidget();\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        IMService im =  IMService.getInstance();\n        im.removeObserver(this);\n        im.removePeerObserver(this);\n        im.removeGroupObserver(this);\n        im.removeSystemObserver(this);\n        Log.i(TAG, \"message list activity destroyed\");\n    }\n\n\n    public  String messageContentToString(MessageContent content) {\n        if (content instanceof Text) {\n            return ((Text) content).text;\n        } else if (content instanceof Image) {\n            return \"一张图片\";\n        } else if (content instanceof Audio) {\n            return \"一段语音\";\n        } else if (content instanceof File) {\n            return \"一个文件\";\n        } else if (content instanceof Video) {\n            return \"一个视频\";\n        } else if (content instanceof com.beetle.bauhinia.db.message.Notification) {\n            return ((com.beetle.bauhinia.db.message.Notification) content).description;\n        } else if (content instanceof Location) {\n            return \"一个地理位置\";\n        } else if (content instanceof GroupVOIP) {\n            return ((GroupVOIP) content).description;\n        } else if (content instanceof VOIP) {\n            VOIP voip = (VOIP) content;\n            if (voip.videoEnabled) {\n                return \"视频聊天\";\n            } else {\n                return \"语音聊天\";\n            }\n        } else if (content instanceof Secret) {\n            return \"消息未能解密\";\n        } else if (content instanceof P2PSession) {\n            return \"\";\n        } else {\n            return \"未知的消息类型\";\n        }\n    }\n\n    void updateConversationDetail(Conversation conv) {\n        String detail = messageContentToString(conv.message.content);\n        conv.setDetail(detail);\n    }\n\n    void updatePeerConversationName(Conversation conv) {\n        User u = getUser(conv.cid);\n        if (TextUtils.isEmpty(u.name)) {\n            conv.setName(u.identifier);\n            final Conversation fconv = conv;\n            asyncGetUser(conv.cid, new GetUserCallback() {\n                @Override\n                public void onUser(User u) {\n                    fconv.setName(u.name);\n                    fconv.setAvatar(u.avatarURL);\n                }\n            });\n        } else {\n            conv.setName(u.name);\n        }\n        conv.setAvatar(u.avatarURL);\n    }\n\n    void updateGroupConversationName(Conversation conv) {\n        Group g = getGroup(conv.cid);\n        if (TextUtils.isEmpty(g.name)) {\n            conv.setName(g.identifier);\n            final Conversation fconv = conv;\n            asyncGetGroup(conv.cid, new GetGroupCallback() {\n                @Override\n                public void onGroup(Group g) {\n                    fconv.setName(g.name);\n                    fconv.setAvatar(g.avatarURL);\n                }\n            });\n        } else {\n            conv.setName(g.name);\n        }\n        conv.setAvatar(g.avatarURL);\n    }\n\n    void loadConversations() {\n        conversations = ConversationDB.getInstance().getConversations();\n        boolean customerExists = false;\n        for (Conversation conv : conversations) {\n            if (conv.type == Conversation.CONVERSATION_PEER) {\n                IMessage msg = PeerMessageDB.getInstance().getLastMessage(conv.cid);\n                conv.message = msg;\n\n                updatePeerConversationName(conv);\n                updateNotificationDesc(conv);\n                updateConversationDetail(conv);\n            } else if (conv.type == Conversation.CONVERSATION_PEER_SECRET) {\n                IMessage msg = EPeerMessageDB.getInstance().getLastMessage(conv.cid);\n                conv.message = msg;\n\n                updatePeerConversationName(conv);\n                updateNotificationDesc(conv);\n                updateConversationDetail(conv);\n            } else if (conv.type == Conversation.CONVERSATION_GROUP) {\n                IMessage msg = GroupMessageDB.getInstance().getLastMessage(conv.cid);\n                conv.message = msg;\n\n                updateGroupConversationName(conv);\n                updateNotificationDesc(conv);\n                updateConversationDetail(conv);\n            }\n        }\n\n\n        Comparator<Conversation> cmp = new Comparator<Conversation>() {\n            public int compare(Conversation c1, Conversation c2) {\n\n                int t1 = 0;\n                int t2 = 0;\n                if (c1.message != null) {\n                    t1 = c1.message.timestamp;\n                }\n                if (c2.message != null) {\n                    t2 = c2.message.timestamp;\n                }\n\n                if (t1 > t2) {\n                    return -1;\n                } else if (t1 == t2) {\n                    return 0;\n                } else {\n                    return 1;\n                }\n\n            }\n        };\n        Collections.sort(conversations, cmp);\n    }\n\n    public static class User {\n        public long uid;\n        public String name;\n        public String avatarURL;\n\n        //name为nil时，界面显示identifier字段\n        public String identifier;\n    }\n\n    public static class Group {\n        public long gid;\n        public String name;\n        public String avatarURL;\n\n        //name为nil时，界面显示identifier字段\n        public String identifier;\n    }\n\n\n\n    public interface GetUserCallback {\n        void onUser(User u);\n    }\n\n    public interface GetGroupCallback {\n        void onGroup(Group g);\n    }\n\n    @Override\n    public void onItemClick(AdapterView<?> parent, View view, int position,\n                            long id) {\n        Conversation conv = conversations.get(position);\n        Log.i(TAG, \"conv:\" + conv.getName());\n\n        if (conv.type == Conversation.CONVERSATION_PEER) {\n            onPeerClick(conv.cid);\n        } else if (conv.type == Conversation.CONVERSATION_GROUP){\n            onGroupClick(conv.cid);\n        }\n    }\n\n    @Override\n    public void onConnectState(IMService.ConnectState state) {\n\n    }\n\n\n\n    public Conversation findConversation(long cid, int type) {\n        for (int i = 0; i < conversations.size(); i++) {\n            Conversation conv = conversations.get(i);\n            if (conv.cid == cid && conv.type == type) {\n                return conv;\n            }\n        }\n        return null;\n    }\n\n    public int findConversationPosition(long cid, int type) {\n        for (int i = 0; i < conversations.size(); i++) {\n            Conversation conv = conversations.get(i);\n            if (conv.cid == cid && conv.type == type) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    public Conversation newPeerConversation(long cid) {\n        Conversation conversation = new Conversation();\n        conversation.type = Conversation.CONVERSATION_PEER;\n        conversation.cid = cid;\n\n        updatePeerConversationName(conversation);\n        ConversationDB.getInstance().addConversation(conversation);\n        return conversation;\n    }\n\n    public Conversation newGroupConversation(long cid) {\n        Conversation conversation = new Conversation();\n        conversation.type = Conversation.CONVERSATION_GROUP;\n        conversation.cid = cid;\n        updateGroupConversationName(conversation);\n        ConversationDB.getInstance().addConversation(conversation);\n        return conversation;\n    }\n\n    public static int now() {\n        Date date = new Date();\n        long t = date.getTime();\n        return (int)(t/1000);\n    }\n\n\n    @Override\n    public void onPeerSecretMessage(IMMessage msg) {\n\n    }\n\n    @Override\n    public void onPeerMessage(IMMessage msg) {\n        Log.i(TAG, \"on peer message\");\n        IMessage imsg = new IMessage();\n        imsg.timestamp = now();\n        imsg.msgLocalID = msg.msgLocalID;\n        imsg.sender = msg.sender;\n        imsg.receiver = msg.receiver;\n        imsg.setContent(msg.content);\n\n        long cid = 0;\n        if (msg.sender == this.currentUID) {\n            cid = msg.receiver;\n        } else {\n            cid = msg.sender;\n        }\n\n        int pos = findConversationPosition(cid, Conversation.CONVERSATION_PEER);\n        Conversation conversation = null;\n        if (pos == -1) {\n            conversation = newPeerConversation(cid);\n        } else {\n            conversation = conversations.get(pos);\n        }\n\n        conversation.message = imsg;\n        updateConversationDetail(conversation);\n\n        if (pos == -1) {\n            conversations.add(0, conversation);\n            adapter.notifyDataSetChanged();\n        } else if (pos > 0) {\n            conversations.remove(pos);\n            conversations.add(0, conversation);\n            adapter.notifyDataSetChanged();\n        } else {\n            //pos == 0\n        }\n    }\n\n\n    @Override\n    public void onPeerMessageACK(IMMessage im, int error) {\n        Log.i(TAG, \"message ack on main\");\n\n        long msgLocalID = im.msgLocalID;\n        long uid = im.receiver;\n        if (msgLocalID == 0) {\n            MessageContent c = IMessage.fromRaw(im.plainContent);\n            if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                Revoke r = (Revoke)c;\n                int pos = -1;\n                if (!im.secret) {\n                    pos = findConversationPosition(uid, Conversation.CONVERSATION_PEER);\n                } else {\n                    pos = findConversationPosition(uid, Conversation.CONVERSATION_PEER_SECRET);\n                }\n                Conversation conversation = conversations.get(pos);\n                if (r.msgid.equals(conversation.message.getUUID())) {\n                    conversation.message.setContent(r);\n                    updateNotificationDesc(conversation);\n                    updateConversationDetail(conversation);\n                }\n            }\n        }\n    }\n\n    @Override\n    public void onPeerMessageFailure(IMMessage im) {\n\n    }\n\n\n    @Override\n    public void onGroupMessages(List<IMMessage> msgs) {\n        HashMap<Long, Integer> unreadDict = new HashMap<>();\n        HashMap<Long, IMMessage> msgDict = new HashMap<>();\n\n        for (IMMessage msg : msgs) {\n            int count = 0;\n            if (unreadDict.containsKey(msg.receiver)) {\n                count = (Integer)unreadDict.get(msg.receiver);\n            }\n            if (!msg.isGroupNotification && msg.sender != this.currentUID) {\n                count += 1;\n            }\n\n            unreadDict.put(msg.receiver, count);\n            msgDict.put(msg.receiver, msg);\n        }\n\n        Iterator iter = msgDict.entrySet().iterator();\n        while (iter.hasNext()) {\n            Map.Entry<Long, IMMessage> entry = (Map.Entry<Long, IMMessage>)iter.next();\n\n            IMMessage im = entry.getValue();\n            Long groupId = entry.getKey();\n            int unread = unreadDict.get(groupId);\n            onGroupMessage(im, unread);\n        }\n    }\n\n\n    public void onGroupMessage(IMMessage msg, int unread) {\n        Log.i(TAG, \"on group message\");\n        IMessage imsg = new IMessage();\n        imsg.timestamp = msg.timestamp;\n        imsg.msgLocalID = msg.msgLocalID;\n        imsg.sender = msg.sender;\n        imsg.receiver = msg.receiver;\n        if (msg.isGroupNotification) {\n            GroupNotification groupNotification = GroupNotification.newGroupNotification(msg.content);\n            imsg.receiver = groupNotification.groupID;\n            imsg.timestamp = groupNotification.timestamp;\n            imsg.setContent(groupNotification);\n        } else {\n            imsg.setContent(msg.content);\n        }\n\n        int pos = findConversationPosition(msg.receiver, Conversation.CONVERSATION_GROUP);\n        Conversation conversation = null;\n        if (pos == -1) {\n            conversation = newGroupConversation(msg.receiver);\n        } else {\n            conversation = conversations.get(pos);\n        }\n\n        conversation.message = imsg;\n        updateConversationDetail(conversation);\n\n        if (pos == -1) {\n            conversations.add(0, conversation);\n            adapter.notifyDataSetChanged();\n        } else if (pos > 0) {\n            conversations.remove(pos);\n            conversations.add(0, conversation);\n            adapter.notifyDataSetChanged();\n        } else {\n            //pos == 0\n        }\n    }\n\n    @Override\n    public void onGroupMessageACK(IMMessage im, int error) {\n        long msgLocalID = im.msgLocalID;\n        long gid = im.receiver;\n        if (msgLocalID == 0) {\n            MessageContent c = IMessage.fromRaw(im.content);\n            if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                Revoke r = (Revoke)c;\n                int pos = -1;\n                pos = findConversationPosition(gid, Conversation.CONVERSATION_GROUP);\n                Conversation conversation = conversations.get(pos);\n                if (r.msgid.equals(conversation.message.getUUID())) {\n                    conversation.message.setContent(r);\n                    updateNotificationDesc(conversation);\n                    updateConversationDetail(conversation);\n                }\n            }\n        }\n    }\n\n    @Override\n    public void onGroupMessageFailure(IMMessage im) {\n\n    }\n\n\n    private void updateNotificationDesc(Conversation conv) {\n        final IMessage imsg = conv.message;\n        if (imsg == null || imsg.content.getType() != MessageContent.MessageType.MESSAGE_GROUP_NOTIFICATION) {\n            return;\n        }\n        long currentUID = this.currentUID;\n        GroupNotification notification = (GroupNotification)imsg.content;\n        if (notification.notificationType == GroupNotification.NOTIFICATION_GROUP_CREATED) {\n            if (notification.master == currentUID) {\n                notification.description = String.format(\"您创建了\\\"%s\\\"群组\", notification.groupName);\n            } else {\n                notification.description = String.format(\"您加入了\\\"%s\\\"群组\", notification.groupName);\n            }\n        } else if (notification.notificationType == GroupNotification.NOTIFICATION_GROUP_DISBAND) {\n            notification.description = \"群组已解散\";\n        } else if (notification.notificationType == GroupNotification.NOTIFICATION_GROUP_MEMBER_ADDED) {\n            User u = getUser(notification.member);\n            if (TextUtils.isEmpty(u.name)) {\n                notification.description = String.format(\"\\\"%s\\\"加入群\", u.identifier);\n                final GroupNotification fnotification = notification;\n                final Conversation fconv = conv;\n                asyncGetUser(notification.member, new GetUserCallback() {\n                    @Override\n                    public void onUser(User u) {\n                        fnotification.description = String.format(\"\\\"%s\\\"加入群\", u.name);\n                        if (fconv.message == imsg) {\n                            fconv.setDetail(fnotification.description);\n                        }\n                    }\n                });\n            } else {\n                notification.description = String.format(\"\\\"%s\\\"加入群\", u.name);\n            }\n        } else if (notification.notificationType == GroupNotification.NOTIFICATION_GROUP_MEMBER_LEAVED) {\n            User u = getUser(notification.member);\n            if (TextUtils.isEmpty(u.name)) {\n                notification.description = String.format(\"\\\"%s\\\"离开群\", u.identifier);\n                final GroupNotification fnotification = notification;\n                final Conversation fconv = conv;\n                asyncGetUser(notification.member, new GetUserCallback() {\n                    @Override\n                    public void onUser(User u) {\n                        fnotification.description = String.format(\"\\\"%s\\\"离开群\", u.name);\n                        if (fconv.message == imsg) {\n                            fconv.setDetail(fnotification.description);\n                        }\n                    }\n                });\n            } else {\n                notification.description = String.format(\"\\\"%s\\\"离开群\", u.name);\n            }\n        } else if (notification.notificationType == GroupNotification.NOTIFICATION_GROUP_NAME_UPDATED) {\n            notification.description = String.format(\"群组改名为\\\"%s\\\"\", notification.groupName);\n        }\n    }\n\n\n\n    @Override\n    public void onSystemMessage(String sm) {\n        Log.i(TAG, \"system message:\" + sm);\n    }\n\n    public boolean canBack() {\n        return false;\n    }\n\n\n\n\n\n    protected User getUser(long uid) {\n        User u = new User();\n        u.uid = uid;\n        u.name = null;\n        u.avatarURL = \"\";\n        u.identifier = String.format(\"%d\", uid);\n        return u;\n    }\n\n\n    protected void asyncGetUser(long uid, GetUserCallback cb) {\n        final long fuid = uid;\n        final GetUserCallback fcb = cb;\n        new AsyncTask<Void, Integer, User>() {\n            @Override\n            protected User doInBackground(Void... urls) {\n                User u = new User();\n                u.uid = fuid;\n                u.name = String.format(\"%d\", fuid);\n                u.avatarURL = \"\";\n                u.identifier = String.format(\"%d\", fuid);\n                return u;\n            }\n            @Override\n            protected void onPostExecute(User result) {\n                fcb.onUser(result);\n            }\n        }.execute();\n    }\n\n\n    protected Group getGroup(long gid) {\n        Group g = new Group();\n        g.gid = gid;\n        g.name = null;\n        g.avatarURL = \"\";\n        g.identifier = String.format(\"%d\", gid);\n        return g;\n    }\n\n\n    protected void asyncGetGroup(long gid, GetGroupCallback cb) {\n        final long fgid = gid;\n        final GetGroupCallback fcb = cb;\n        new AsyncTask<Void, Integer, Group>() {\n            @Override\n            protected Group doInBackground(Void... urls) {\n                Group g = new Group();\n                g.gid = fgid;\n                g.name = String.format(\"%d\", fgid);\n                g.avatarURL = \"\";\n                g.identifier = String.format(\"%d\", fgid);\n                return g;\n            }\n            @Override\n            protected void onPostExecute(Group result) {\n                fcb.onGroup(result);\n            }\n        }.execute();\n    }\n\n\n    protected void onPeerClick(long uid) {\n        User u = getUser(uid);\n\n        Intent intent = new Intent(this, PeerMessageActivity.class);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        intent.putExtra(\"peer_uid\", uid);\n        if (TextUtils.isEmpty(u.name)) {\n            intent.putExtra(\"peer_name\", u.identifier);\n        } else {\n            intent.putExtra(\"peer_name\", u.name);\n        }\n        intent.putExtra(\"current_uid\", this.currentUID);\n        startActivity(intent);\n    }\n\n\n    protected void onGroupClick(long gid) {\n        Log.i(TAG, \"group conversation\");\n    }\n\n\n\n\n}\n"
  },
  {
    "path": "app/src/io/gobelieve/im/demo/model/Conversation.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo.model;\n\nimport com.beetle.bauhinia.db.IMessage;\n\nimport java.beans.PropertyChangeListener;\nimport java.beans.PropertyChangeSupport;\nimport java.util.ArrayList;\n\n/**\n * Created by houxh on 15/3/9.\n */\npublic class Conversation {\n    public static final int CONVERSATION_PEER = 1;\n    public static final int CONVERSATION_GROUP = 2;\n    public static final int CONVERSATION_CUSTOMER_SERVICE = 3;\n    public static final int CONVERSATION_PEER_SECRET = 4;//点对点加密会话\n\n    public static final int STATE_UNINITIALIZE = 0;//未初始化状态\n    public static final int STATE_WAIT = 1;//等待对方上线\n    public static final int STATE_EXCHANGE = 2;//交换密钥中,暂时未被使用\n    public static final int STATE_CONNECTED = 3;//连接成功，可以发送加密消息\n\n\n    public long rowid;\n    public int type;\n    public long cid;\n    public int state;//p2p加密会话的状态,普通会话此字段无意义\n    public IMessage message;\n\n    //search\n    public ArrayList<IMessage> messages = new ArrayList<>();\n\n    private String name;\n    private String avatar;\n    private String detail;\n    private int unreadCount;\n\n    private PropertyChangeSupport changeSupport = new PropertyChangeSupport(\n            this);\n\n    public void addPropertyChangeListener(PropertyChangeListener listener) {\n        changeSupport.addPropertyChangeListener(listener);\n    }\n\n    public void removePropertyChangeListener(PropertyChangeListener listener) {\n        changeSupport.removePropertyChangeListener(listener);\n    }\n\n    public void addPropertyChangeListener(String propertyName,\n                                          PropertyChangeListener listener) {\n        changeSupport.addPropertyChangeListener(propertyName, listener);\n    }\n\n\n    public void setName(String name) {\n        String old = this.name;\n        this.name = name;\n        changeSupport.firePropertyChange(\"name\", old, this.name);\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public void setAvatar(String avatar) {\n        String old = this.avatar;\n        this.avatar = avatar;\n        changeSupport.firePropertyChange(\"avatar\", old, this.avatar);\n    }\n\n    public String getAvatar() {\n        return this.avatar;\n    }\n\n    public void setDetail(String detail) {\n        String old = this.detail;\n        this.detail = detail;\n        changeSupport.firePropertyChange(\"detail\", old, this.detail);\n    }\n\n    public String getDetail() {\n        return this.detail;\n    }\n\n    public void setUnreadCount(int unreadCount) {\n        int old = this.unreadCount;\n        this.unreadCount = unreadCount;\n        changeSupport.firePropertyChange(\"unreadCount\", old, this.unreadCount);\n    }\n\n    public int getUnreadCount() {\n        return this.unreadCount;\n    }\n}\n"
  },
  {
    "path": "app/src/io/gobelieve/im/demo/model/ConversationDB.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo.model;\n\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.util.Log;\n\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class ConversationDB {\n\n    private static final String TAG = \"goubuli\";\n    private static final String TABLE_NAME = \"conversation\";\n\n    public static final String COLUMN_ROWID = \"id\";\n    public static final String COLUMN_STATE = \"state\";\n    public static final String COLUMN_UNREAD = \"unread\";\n\n\n    private static ConversationDB instance = new ConversationDB();\n    public static ConversationDB getInstance() {\n        return instance;\n    }\n\n    private SQLiteDatabase mDB;\n\n    public void setDb(SQLiteDatabase db) {\n        this.mDB = db;\n    }\n\n    public SQLiteDatabase getDb() {\n        return this.mDB;\n    }\n\n    public List<Conversation> getConversations() {\n        ArrayList<Conversation> convs = new ArrayList<>();\n        Cursor cursor = null;\n        try {\n            cursor = mDB.query(TABLE_NAME, new String[]{\"id\", \"cid\", \"type\", \"name\", \"state\", \"unread\"},\n                    null, null, null, null, null);\n            while (cursor.moveToNext()) {\n                long rowid = cursor.getLong(cursor.getColumnIndex(\"id\"));\n                long id = cursor.getLong(cursor.getColumnIndex(\"cid\"));\n                int type = cursor.getInt(cursor.getColumnIndex(\"type\"));\n                String name = cursor.getString(cursor.getColumnIndex(\"name\"));\n                int state = cursor.getInt(cursor.getColumnIndex(\"state\"));\n                int unread = cursor.getInt(cursor.getColumnIndex(\"unread\"));\n                Conversation conv = new Conversation();\n                conv.rowid = rowid;\n                conv.cid = id;\n                conv.type = type;\n                conv.state = state;\n                conv.setUnreadCount(unread);\n                conv.setName(name);\n                convs.add(conv);\n            }\n        } catch (Exception ex) {\n            Log.e(TAG, ex.toString());\n        } finally {\n            if (null != cursor) {\n                cursor.close();\n            }\n        }\n        return convs;\n    }\n\n\n    public Conversation getConversation(long cid, int type) {\n        Cursor cursor = null;\n        try {\n\n            cursor = mDB.query(TABLE_NAME, new String[]{\"id\", \"cid\", \"type\", \"name\", \"unread\"},\n                    \"cid=? AND type=?\",\n                    new String[]{\n                            String.valueOf(cid),\n                            String.valueOf(type)\n                    },\n                    null, null, null);\n            if (cursor.moveToNext()) {\n                long rowid = cursor.getLong(cursor.getColumnIndex(\"id\"));\n                String name = cursor.getString(cursor.getColumnIndex(\"name\"));\n                int state = cursor.getInt(cursor.getColumnIndex(\"state\"));\n                int unread = cursor.getInt(cursor.getColumnIndex(\"unread\"));\n                Conversation conv = new Conversation();\n                conv.rowid = rowid;\n                conv.cid = cid;\n                conv.type = type;\n                conv.state = state;\n                conv.setUnreadCount(unread);\n                conv.setName(name);\n                return conv;\n            }\n        } catch (Exception ex) {\n            Log.e(TAG, ex.toString());\n        } finally {\n            if (null != cursor) {\n                cursor.close();\n            }\n        }\n        return null;\n    }\n\n    public boolean addConversation(Conversation conv) {\n        boolean result = true;\n\n        try {\n            mDB.beginTransaction();\n            ContentValues values = new ContentValues();\n            values.put(\"cid\", conv.cid);\n            values.put(\"type\", conv.type);\n            values.put(\"name\", conv.getName());\n            values.put(\"state\", conv.state);\n            values.put(\"unread\", conv.getUnreadCount());\n            conv.rowid = mDB.insert(TABLE_NAME, null, values);\n            values.clear();\n            mDB.setTransactionSuccessful();\n        } catch (Exception e) {\n            e.printStackTrace();\n            result = false;\n        } finally {\n            mDB.endTransaction();\n        }\n        return result;\n    }\n\n    public boolean setNewCount(long rowid, int count) {\n        boolean result;\n        try {\n\n            ContentValues values = new ContentValues();\n            values.put(COLUMN_UNREAD, count);\n\n            int r = mDB.update(TABLE_NAME,\n                    values, \"id=?\",\n                    new String[] {\n                        String.valueOf(rowid)\n                    });\n\n            result = r > 0;\n        } catch (Exception e) {\n            Log.e(TAG, e.toString());\n            result = false;\n        }\n        return result;\n    }\n\n\n    public boolean setState(long rowid, int state) {\n        boolean result;\n        try {\n\n            ContentValues values = new ContentValues();\n            values.put(COLUMN_STATE, state);\n\n            int r = mDB.update(TABLE_NAME,\n                    values, \"id=?\",\n                    new String[] {\n                            String.valueOf(rowid)\n                    });\n\n            result = r > 0;\n        } catch (Exception e) {\n            Log.e(TAG, e.toString());\n            result = false;\n        }\n        return result;\n    }\n\n    public boolean resetState(int state) {\n        boolean result = true;\n        try {\n\n            ContentValues values = new ContentValues();\n            values.put(COLUMN_STATE, state);\n\n            mDB.update(TABLE_NAME,\n                    values, null,\n                    null);\n\n        } catch (Exception e) {\n            Log.e(TAG, e.toString());\n            result = false;\n        }\n        return result;\n    }\n\n\n    public boolean removeConversation(Conversation conv) {\n\n        try {\n            mDB.beginTransaction();\n\n            mDB.delete(TABLE_NAME, \"id=?\",\n                    new String[] {\n                            String.valueOf(conv.rowid)\n                    });\n            mDB.setTransactionSuccessful();\n        } catch (Exception e) {\n            e.printStackTrace();\n            if (mDB.inTransaction()) {\n                mDB.endTransaction();\n            }\n            return false;\n        } finally {\n            if (mDB.inTransaction()) {\n                mDB.endTransaction();\n            }\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "app/src/io/gobelieve/im/demo/model/MessageDatabaseHelper.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo.model;\n\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.util.Log;\n\npublic class MessageDatabaseHelper {\n    private static final String TAG = \"goubuli\";\n\n    private static final int DATABASE_VERSION  = 3;\n\n    private static final Object lock = new Object();\n\n    private static MessageDatabaseHelper instance;\n    private DatabaseHelper databaseHelper;\n\n\n    private static class DatabaseHelper extends SQLiteOpenHelper {\n\n        DatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {\n            super(context, name, factory, version);\n        }\n\n        @Override\n        public void onCreate(SQLiteDatabase db) {\n            db.beginTransaction();\n            createDatabase(db);\n            db.setTransactionSuccessful();\n            db.endTransaction();\n        }\n\n        @Override\n        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n            Log.d(TAG, \"upgrade database, old version:\" + oldVersion + \" new version:\" + newVersion);\n            assert(newVersion == 3);\n        }\n\n        private void createDatabase(SQLiteDatabase db) {\n            db.execSQL(SQLCreator.PEER_MESSAGE);\n            db.execSQL(SQLCreator.GROUP_MESSAGE);\n            db.execSQL(SQLCreator.CUSTOMER_MESSAGE);\n            db.execSQL(SQLCreator.PEER_MESSAGE_FTS);\n            db.execSQL(SQLCreator.GROUP_MESSAGE_FTS);\n            db.execSQL(SQLCreator.CUSTOMER_MESSAGE_FTS);\n            db.execSQL(SQLCreator.GROUP_MESSAGE_READED);\n\n            db.execSQL(SQLCreator.PEER_MESSAGE_IDX);\n            db.execSQL(SQLCreator.PEER_MESSAGE_UUID_IDX);\n            db.execSQL(SQLCreator.GROUP_MESSAGE_IDX);\n            db.execSQL(SQLCreator.GROUP_MESSAGE_UUID_IDX);\n\n            db.execSQL(SQLCreator.CUSTOMER_MESSAGE_IDX);\n            db.execSQL(SQLCreator.CUSTOMER_PEER_MESSAGE_IDX);\n            db.execSQL(SQLCreator.CUSTOMER_MESSAGE_UUID_IDX);\n\n            db.execSQL(SQLCreator.CONVERSATION);\n            db.execSQL(SQLCreator.CONVERSATION_IDX);\n        }\n    }\n\n    public static MessageDatabaseHelper getInstance() {\n        synchronized (lock) {\n            if (instance == null)\n                instance = new MessageDatabaseHelper();\n            return instance;\n        }\n    }\n\n    private MessageDatabaseHelper() {\n\n    }\n\n    public void open(Context context, String name) {\n        if (this.databaseHelper != null) {\n            this.databaseHelper.close();\n        }\n        this.databaseHelper = new DatabaseHelper(context, name, null, DATABASE_VERSION);\n    }\n\n    public SQLiteDatabase getDatabase() {\n        return this.databaseHelper.getWritableDatabase();\n    }\n\n    public void close() {\n        if (this.databaseHelper != null) {\n            this.databaseHelper.close();\n            this.databaseHelper = null;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/io/gobelieve/im/demo/model/SQLCreator.java",
    "content": "/*                                                                            \r\n  Copyright (c) 2014-2019, GoBelieve     \r\n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\r\n \r\n  This source code is licensed under the BSD-style license found in the\r\n  LICENSE file in the root directory of this source tree. An additional grant\r\n  of patent rights can be found in the PATENTS file in the same directory.\r\n*/\r\n\r\n\r\npackage io.gobelieve.im.demo.model;\r\n\r\npublic class SQLCreator {\r\n    //cid:peer_uid|group_id|store_id\r\n    public final static String CONVERSATION = \"CREATE TABLE IF NOT EXISTS \\\"conversation\\\" \"\r\n            + \"(\\\"id\\\" INTEGER PRIMARY KEY NOT NULL , \"\r\n            + \"\\\"appid\\\" INTEGER DEFAULT 0, \"\r\n            + \"\\\"target\\\" INTEGER NOT NULL, \"\r\n            + \"\\\"type\\\" INTEGER NOT NULL, \"\r\n            + \"\\\"name\\\" VARCHAR(255), \"\r\n            + \"\\\"attrs\\\" TEXT, \"\r\n            + \"\\\"flags\\\" INTEGER DEFAULT 0, \"\r\n            + \"\\\"detail\\\" TEXT, \"\r\n            + \"\\\"state\\\" INTEGER DEFAULT 0, \"\r\n            + \"\\\"timestamp\\\" INTEGER DEFAULT 0, \"\r\n            + \"\\\"unread\\\" INTEGER DEFAULT 0) \";\r\n\r\n\r\n    public final static String CONVERSATION_IDX = \"CREATE UNIQUE INDEX [conversation_idx] On [conversation] ( [appid], [target], [type] );\";\r\n\r\n    public final static String PEER_MESSAGE = \"CREATE TABLE \\\"peer_message\\\" ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `peer` INTEGER NOT NULL, `secret` INTEGER DEFAULT 0, `sender` INTEGER NOT NULL, `receiver` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `flags` INTEGER NOT NULL, `content` TEXT, `uuid` TEXT );\";\r\n\r\n    public final static String GROUP_MESSAGE = \"CREATE TABLE \\\"group_message\\\" ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `sender` INTEGER NOT NULL, `group_id` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `flags` INTEGER NOT NULL, `reader_count` INTEGER DEFAULT 0, `content` TEXT, `uuid` TEXT, `reference_count` INTEGER DEFAULT 0, `reference` TEXT,  `tags` TEXT  );\";\r\n\r\n    public final static String CUSTOMER_MESSAGE = \"CREATE TABLE \\\"customer_message\\\" ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `peer_appid` INTEGER NOT NULL, `peer` INTEGER NOT NULL, `store_id` INTEGER NOT NULL, `sender_appid` INTEGER NOT NULL, `sender` INTEGER NOT NULL, `receiver_appid` INTEGER NOT NULL,  `receiver` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `flags` INTEGER NOT NULL, `content` TEXT, `uuid` TEXT );\";\r\n\r\n    public final static String PEER_MESSAGE_FTS = \"CREATE VIRTUAL TABLE peer_message_fts USING fts4(content TEXT);\";\r\n\r\n    public final static String GROUP_MESSAGE_FTS = \"CREATE VIRTUAL TABLE group_message_fts USING fts4(content TEXT);\";\r\n\r\n    public final static String CUSTOMER_MESSAGE_FTS = \"CREATE VIRTUAL TABLE customer_message_fts USING fts4(content TEXT);\";\r\n\r\n    public final static String PEER_MESSAGE_IDX = \"CREATE INDEX `peer_index` ON `peer_message` (`peer`, `secret`, `id`);\";\r\n    public final static String GROUP_MESSAGE_IDX = \"CREATE INDEX `group_index` ON `group_message` (`group_id`);\";\r\n    public final static String CUSTOMER_MESSAGE_IDX = \"CREATE INDEX `customer_index` ON `customer_message` (`store_id`);\";\r\n    public final static String CUSTOMER_PEER_MESSAGE_IDX = \"CREATE INDEX `customer_peer_index` ON `customer_message` (`peer_appid`, `peer`);\";\r\n\r\n    public final static String PEER_MESSAGE_UUID_IDX = \"CREATE UNIQUE INDEX `peer_uuid_index` ON `peer_message` (`uuid`)\";\r\n    public final static String GROUP_MESSAGE_UUID_IDX = \"CREATE UNIQUE INDEX `group_uuid_index` ON `group_message` (`uuid`)\";\r\n    public final static String CUSTOMER_MESSAGE_UUID_IDX = \"CREATE UNIQUE INDEX `customer_uuid_index` ON `customer_message` (`uuid`)\";\r\n\r\n\r\n    public final static String GROUP_MESSAGE_READED = \"CREATE TABLE \\\"group_message_readed\\\" ( `msg_id` INTEGER NOT NULL, `uid` INTEGER NOT NULL, PRIMARY KEY(`msg_id`,`uid`) )\";\r\n\r\n}\r\n"
  },
  {
    "path": "asynctcp/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "asynctcp/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n    buildToolsVersion rootProject.ext.buildToolsVersion\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_6\n        targetCompatibility JavaVersion.VERSION_1_6\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n\n    sourceSets {\n        main {\n            jniLibs.srcDirs = ['libs']\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n}\n"
  },
  {
    "path": "asynctcp/src/androidTest/java/com/beetle/asynctcp/ApplicationTest.java",
    "content": "package com.beetle.asynctcp;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "asynctcp/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.beetle.asynctcp\">\n\n\n</manifest>\n"
  },
  {
    "path": "asynctcp/src/main/java/com/beetle/AsyncSSLTCP.java",
    "content": "package com.beetle;\n\npublic class AsyncSSLTCP implements AsyncTCPInterface {\n    private int sock;\n    private int events;\n    private boolean closed;\n    private int state;\n    private long ssl;\n    private long sslCTX;\n\n    private byte[] data;\n    \n    private TCPConnectCallback connectCallback;\n    private TCPReadCallback readCallback;\n    private long self;\n   \n\n    public void setConnectCallback(TCPConnectCallback cb) {\n\tconnectCallback = cb;\n    }\n    public void setReadCallback(TCPReadCallback cb) {\n\treadCallback = cb;\n    }\n    public native boolean connect(String host, int port);\n    public native void close();\n    public native void release();\n\n    public native void writeData(byte[] bytes);\n    \n    public native void startRead();\n  \n\n    static {\n        System.loadLibrary(\"async_ssl_tcp\");\n    }\n}\n"
  },
  {
    "path": "asynctcp/src/main/java/com/beetle/AsyncTCP.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle;\n\nimport java.lang.ref.WeakReference;\n\npublic class AsyncTCP implements AsyncTCPInterface {\n    private int sock;\n    private int events;\n    private boolean closed;\n\n    private byte[] data;\n    private boolean connecting;\n    \n    private TCPConnectCallback connectCallback;\n    private TCPReadCallback readCallback;\n    private long self;\n   \n\n    public void setConnectCallback(TCPConnectCallback cb) {\n\tconnectCallback = cb;\n    }\n    public void setReadCallback(TCPReadCallback cb) {\n\treadCallback = cb;\n    }\n    public native boolean connect(String host, int port);\n    public native void close();\n    public native void release();\n\n    public native void writeData(byte[] bytes);\n    \n    public native void startRead();\n  \n\n    static {\n        System.loadLibrary(\"async_tcp\");\n    }\n}\n"
  },
  {
    "path": "asynctcp/src/main/java/com/beetle/AsyncTCPInterface.java",
    "content": "package com.beetle;\n\npublic interface AsyncTCPInterface {\n\n    public void setConnectCallback(TCPConnectCallback cb);\n    public void setReadCallback(TCPReadCallback cb);\n    public boolean connect(String host, int port);\n\n    // close async tcp only stop read&write.\n    public void close();\n\n    // release the underline resource, like socket fd, java object weak reference.\n    public void release();\n\n    public void writeData(byte[] bytes);\n\n    public void startRead();\n}\n"
  },
  {
    "path": "asynctcp/src/main/java/com/beetle/AsyncTCPTest.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport\tandroid.os.Looper;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.View.OnClickListener;\nimport android.widget.Button;\n\n\n\npublic class AsyncTCPTest extends Activity  {\n\tAsyncTCP tcp;\n\tbyte[] recvBuf = new byte[0];\n    @Override\n    public void onCreate(Bundle savedInstanceState)\n    {\n        super.onCreate(savedInstanceState);\n        Button bt = new Button(this);\n        bt.setText( \"start\" );\n        setContentView(bt);\n        \n                \n        bt.setOnClickListener(new OnClickListener() {\n\t\t\t@Override\n\t\t\tpublic void onClick(View arg0) {\n\t\t\t\ttest();\n\t\t\t}\n\t\t});\n    }\n    \n    \n    public void test() {\n    \tif (tcp != null) return;\n    \ttcp = new AsyncTCP();\n\n    \t\n    \tTCPConnectCallback cb = new TCPConnectCallback() {\n    \t\tpublic void onConnect(Object tcp1, int status) {\n    \t\t    if (status != 0) {\n    \t\t    \tLog.i(\"Beetle\", \"connect error\");\n    \t\t    \ttcp.close();\n    \t\t    \treturn;\n    \t\t    }\n    \t\t    Log.i(\"Beetle\", \"connected\");\n    \t\t    byte[] data = \"GET / HTTP/1.1\\r\\nHost: www.baidu.com\\r\\nConnection: close\\r\\n\\r\\n\".getBytes();\n    \t\t    tcp.writeData(data);\n\n    \t\t    tcp.startRead();\n    \t\t}\n    \t    };\n    \tTCPReadCallback read_cb = new TCPReadCallback() {\n    \t\tpublic void onRead(Object tcp1, byte[] data) {\n    \t\t    if (data.length == 0) {\n    \t\t    \ttry {\n    \t\t    \t\tString result = new String(recvBuf, \"UTF-8\");\n    \t\t    \t\tLog.i(\"Beetle\", result);\n    \t\t    \t} catch(Exception e) {\n\n    \t\t    \t}\n    \t\t    \tLog.i(\"Beetle\", \"tcp closed\");\n    \t\t    \ttcp.close();\n    \t\t    \treturn;\n    \t\t    }\n    \t\t    \n    \t\t    byte[] result = new byte[recvBuf.length + data.length]; \n\t\t        System.arraycopy(recvBuf, 0, result, 0, recvBuf.length); \n\t\t        System.arraycopy(data, 0, result, recvBuf.length, data.length);\n\t\t        recvBuf = result;\n    \t\t    Log.i(\"Beetle\", \"recv data\");\n    \t\t}\t\n    \t\t};\n    \ttcp.setConnectCallback(cb);\n    \ttcp.setReadCallback(read_cb);\n    \ttcp.connect(\"www.baidu.com\", 80);\n    }\n}\n"
  },
  {
    "path": "asynctcp/src/main/java/com/beetle/TCPConnectCallback.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle;\n\npublic interface TCPConnectCallback {\n \n    public void onConnect(Object tcp, int status);\n \n}\n"
  },
  {
    "path": "asynctcp/src/main/java/com/beetle/TCPReadCallback.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle;\n\n \npublic interface TCPReadCallback {\n \n    public void onRead(Object tcp, byte[] data);\n \n}\n"
  },
  {
    "path": "asynctcp/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AsyncTCP</string>\n</resources>\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:4.1.1\"\n    }\n}\n\next {\n    // Compilation (defined here to ensure consistency between the library and sample app)\n    compileSdkVersion = 29\n    buildToolsVersion = '29.0.2'\n\n    //android 6.0\n    minSdkVersion = 23\n    //android 10.0\n    targetSdkVersion = 29\n    javaVersion = JavaVersion.VERSION_1_8\n}\n\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri Aug 23 16:50:47 CST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.5-bin.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Settings specified in this file will override any Gradle settings\n# configured through the IDE.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.enableJetifier=true\nandroid.useAndroidX=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "imkit/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n    buildToolsVersion rootProject.ext.buildToolsVersion\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_7\n        targetCompatibility JavaVersion.VERSION_1_7\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n\n    sourceSets {\n        main {\n            jniLibs.srcDirs = ['libs']\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    implementation project(':imsdk')\n    implementation project(':imlib')\n\n    implementation 'androidx.legacy:legacy-support-v4:1.0.0'\n    implementation 'androidx.appcompat:appcompat:1.0.0'\n    implementation 'androidx.recyclerview:recyclerview:1.0.0';\n    implementation \"androidx.swiperefreshlayout:swiperefreshlayout:1.0.0\"\n    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'\n\n\n    implementation 'com.google.code.gson:gson:2.8.6'\n    implementation 'com.squareup.picasso:picasso:2.71828'\n    implementation 'com.squareup.retrofit:retrofit:1.9.0'\n    implementation 'com.netflix.rxjava:rxjava-core:0.20.7'\n    implementation 'com.netflix.rxjava:rxjava-android:0.20.7'\n    implementation 'com.squareup.okhttp3:okhttp:4.2.2'\n    implementation 'org.apache.commons:commons-io:1.3.2'\n    implementation 'joda-time:joda-time:2.10.5'\n    implementation 'com.commit451:PhotoView:1.2.5'\n    implementation 'cjt.library.wheel:camera:1.1.9'\n    implementation 'com.linkedin.android.spyglass:spyglass:2.0.1'\n    implementation 'co.lujun:androidtagview:1.1.7'\n\n//    implementation 'com.google.code.gson:gson:2.8.5'\n//    implementation 'com.squareup.picasso:picasso:2.71828'\n//    implementation 'com.squareup.retrofit:retrofit:1.9.0'\n//    implementation 'com.netflix.rxjava:rxjava-core:0.20.7'\n//    implementation 'com.netflix.rxjava:rxjava-android:0.20.7'\n//    implementation 'com.squareup.okhttp3:okhttp:4.1.0'\n//    implementation 'org.apache.commons:commons-io:1.3.2'\n//    implementation 'joda-time:joda-time:2.10.3'\n//    implementation 'com.commit451:PhotoView:1.2.5'\n//    implementation 'com.linkedin.android.spyglass:spyglass:1.4.0'\n//    implementation 'cjt.library.wheel:camera:1.1.9'\n\n\n}\n"
  },
  {
    "path": "imkit/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.beetle.imkit\">\n\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"\n                     android:maxSdkVersion=\"18\"/>\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>\n\n    <application>\n\n\n\n    </application>\n\n\n\n</manifest>"
  },
  {
    "path": "imkit/src/main/assets/emoticon",
    "content": "emoticon_0.png,[微笑]\nemoticon_1.png,[撇嘴]\nemoticon_2.png,[色]\nemoticon_3.png,[发呆]\nemoticon_4.png,[得意]\nemoticon_5.png,[流泪]\nemoticon_6.png,[害羞]\nemoticon_7.png,[闭嘴]\nemoticon_8.png,[睡]\nemoticon_9.png,[大哭]\nemoticon_10.png,[尴尬]\nemoticon_11.png,[发怒]\nemoticon_12.png,[调皮]\nemoticon_13.png,[呲牙]\nemoticon_14.png,[惊讶]\nemoticon_15.png,[难过]\nemoticon_16.png,[酷]\nemoticon_17.png,[囧]\nemoticon_18.png,[抓狂]\nemoticon_19.png,[吐]\nemoticon_20.png,[偷笑]\nemoticon_21.png,[愉快]\nemoticon_22.png,[白眼]\nemoticon_23.png,[傲慢]\nemoticon_24.png,[饥饿]\nemoticon_25.png,[困]\nemoticon_26.png,[惊恐]\nemoticon_27.png,[流汗]\nemoticon_28.png,[憨笑]\nemoticon_29.png,[悠闲]\nemoticon_30.png,[奋斗]\nemoticon_31.png,[咒骂]\nemoticon_32.png,[疑问]\nemoticon_33.png,[嘘]\nemoticon_34.png,[晕]\nemoticon_35.png,[疯了]\nemoticon_36.png,[衰]\nemoticon_37.png,[骷髅]\nemoticon_38.png,[敲打]\nemoticon_39.png,[再见]\nemoticon_40.png,[擦汗]\nemoticon_41.png,[抠鼻]\nemoticon_42.png,[鼓掌]\nemoticon_43.png,[糗大了]\nemoticon_44.png,[坏笑]\nemoticon_45.png,[左哼哼]\nemoticon_46.png,[右哼哼]\nemoticon_47.png,[哈欠]\nemoticon_48.png,[鄙视]\nemoticon_49.png,[委屈]\nemoticon_50.png,[快哭了]\nemoticon_51.png,[阴险]\nemoticon_52.png,[亲亲]\nemoticon_53.png,[吓]\nemoticon_54.png,[可怜]\nemoticon_55.png,[菜刀]\nemoticon_56.png,[西瓜]\nemoticon_57.png,[啤酒]\nemoticon_58.png,[篮球]\nemoticon_59.png,[乒乓]\nemoticon_60.png,[咖啡]\nemoticon_61.png,[饭]\nemoticon_62.png,[猪头]\nemoticon_63.png,[玫瑰]\nemoticon_64.png,[凋谢]\nemoticon_65.png,[嘴唇]\nemoticon_66.png,[爱心]\nemoticon_67.png,[心碎]\nemoticon_68.png,[蛋糕]\nemoticon_69.png,[闪电]\nemoticon_70.png,[炸弹]\nemoticon_71.png,[刀]\nemoticon_72.png,[足球]\nemoticon_73.png,[瓢虫]\nemoticon_74.png,[便便]\nemoticon_75.png,[月亮]\nemoticon_76.png,[太阳]\nemoticon_77.png,[礼物]\nemoticon_78.png,[拥抱]\nemoticon_79.png,[强]\nemoticon_80.png,[弱]\nemoticon_81.png,[握手]\nemoticon_82.png,[胜利]\nemoticon_83.png,[抱拳]\nemoticon_84.png,[勾引]\nemoticon_85.png,[拳头]\nemoticon_89.png,[OK]\nemoticon_92.png,[跳跳]\nemoticon_93.png,[发抖]\nemoticon_94.png,[怄火]\nemoticon_95.png,[转圈]\nemoticon_105.png,[嘿哈]\nemoticon_106.png,[捂脸]\nemoticon_107.png,[奸笑]\nemoticon_109.png,[發]\nemoticon_110.png,[福]\nwx_emoji_11.png,[机智]\nwx_emoji_12.png,[皱眉]\nwx_emoji_13.png,[耶]\nwx_emoji_19.png,[红包]"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/ChatItemQuickAction.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\n/**\n * 私聊长按弹出操作\n */\n\npackage com.beetle.bauhinia;\n\nimport android.app.AlertDialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\n\nimport com.beetle.imkit.R;\n\npublic class ChatItemQuickAction {\n    public enum ChatQuickAction {\n        DELETE, FORWARD, RESEND, COPY, REVOKE;\n\n        public String getName(Context context) {\n            switch (this) {\n                case DELETE:\n                    return context.getString(R.string.im_btn_del);\n                case FORWARD:\n                    return context.getString(R.string.im_btn_forward);\n                case RESEND:\n                    return context.getString(R.string.im_btn_resend);\n                case COPY:\n                    return context.getString(R.string.im_btn_copy);\n                case REVOKE:\n                    return context.getString(R.string.im_btn_revoke);\n            }\n            return \"\";\n        }\n    }\n\n    public interface ChatQuickActionResult {\n        void onSelect(ChatQuickAction action);\n    }\n\n    public static void showAction(Context context, final ChatQuickAction[] actions,\n            final ChatQuickActionResult result) {\n        if (actions == null || actions.length < 1)\n            return;\n        AlertDialog.Builder builder = new AlertDialog.Builder(context);\n        builder.setTitle(context.getString(R.string.im_title_choose));\n        CharSequence[] items = new CharSequence[actions.length];\n        for (int i = 0; i < actions.length; ++i) {\n            items[i] = actions[i].getName(context);\n        }\n        builder.setItems(items, new DialogInterface.OnClickListener() {\n            public void onClick(DialogInterface dialog, int item) {\n                if (result != null) {\n                    result.onSelect(actions[item]);\n                }\n            }\n        }).show();\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/CustomerMessageActivity.java",
    "content": "package com.beetle.bauhinia;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.widget.Toast;\n\nimport com.beetle.bauhinia.api.IMHttpAPI;\nimport com.beetle.bauhinia.api.types.Supporter;\nimport com.beetle.bauhinia.db.CustomerMessageDB;\nimport com.beetle.bauhinia.db.ICustomerMessage;\nimport com.beetle.bauhinia.db.MessageFlag;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Revoke;\nimport com.beetle.bauhinia.tools.FileDownloader;\nimport com.beetle.im.CustomerMessage;\nimport com.beetle.im.CustomerMessageObserver;\nimport com.beetle.bauhinia.outbox.CustomerOutbox;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.MessageIterator;\nimport com.beetle.im.IMService;\nimport com.beetle.im.IMServiceObserver;\n\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.functions.Action1;\n\n/**\n * Created by houxh on 16/1/18.\n */\npublic class CustomerMessageActivity extends MessageActivity\n        implements CustomerMessageObserver, IMServiceObserver {\n    protected String sessionID;\n    protected long currentUID;\n    protected long appid;//当前appid\n    protected long peerAppID;\n    protected long peerUID;\n    protected long storeID;\n\n    protected String name;//当前用户昵称\n    protected String appName;\n    protected String peerName;\n    protected String peerAppName;\n    protected String storeName;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        items[ITEM_VIDEO_CALL_ID] = false;\n        super.onCreate(savedInstanceState);\n        isShowUserName = true;\n        isShowReaded = false;\n        isShowReply = false;\n\n        Intent intent = getIntent();\n        appid = intent.getLongExtra(\"app_id\", 0);\n        currentUID = intent.getLongExtra(\"current_uid\", 0);\n        storeID = intent.getLongExtra(\"store_id\", 0);\n        peerUID = intent.getLongExtra(\"peer_uid\", 0);\n        peerAppID = intent.getLongExtra(\"peer_app_id\", 0);\n\n        peerName = intent.getStringExtra(\"peer_name\");\n        if (peerName == null) {\n            peerName = \"\";\n        }\n\n        peerAppName = intent.getStringExtra(\"peer_app_name\");\n        if (peerAppName == null) {\n            peerAppName = \"\";\n        }\n\n        storeName = intent.getStringExtra(\"store_name\");\n        if (storeName == null) {\n            storeName = \"\";\n        }\n\n        sessionID = intent.getStringExtra(\"session_id\");\n        if (sessionID == null) {\n            sessionID = \"\";\n        }\n        appName = intent.getStringExtra(\"app_name\");\n        if (appName == null) {\n            appName = \"\";\n        }\n        name = intent.getStringExtra(\"name\");\n        if (name == null) {\n            name = \"\";\n        }\n\n        if (appid == 0 || currentUID == 0) {\n            Log.e(TAG, \"invalid param\");\n            finish();\n            return;\n        }\n\n        Log.i(TAG, \"appid:\" + appid +\" uid:\" + currentUID + \" peer app id:\" + peerAppID + \" peer uid:\" + peerUID + \" store id:\" + storeID);\n\n        this.conversationID = \"\" + peerAppID + \"_\" + peerUID;\n        this.messageDB = CustomerMessageDB.getInstance();\n        this.hasLateMore = this.messageID > 0;\n        this.hasEarlierMore = true;\n        this.loadData();\n\n        if (!TextUtils.isEmpty(storeName)) {\n            getSupportActionBar().setTitle(storeName);\n        }\n        //显示最后一条消息\n        if (this.messages.size() > 0) {\n            listview.setSelection(this.messages.size() - 1);\n        }\n\n        CustomerOutbox.getInstance().addObserver(this);\n        IMService.getInstance().addObserver(this);\n        IMService.getInstance().addCustomerServiceObserver(this);\n        FileDownloader.getInstance().addObserver(this);\n\n        if (peerAppID == 0 || peerUID == 0) {\n            this.disableSend();\n\n            IMHttpAPI.Singleton().getSupporter(this.storeID)\n                    .observeOn(AndroidSchedulers.mainThread())\n                    .subscribe(new Action1<Supporter>() {\n                        @Override\n                        public void call(Supporter supporter) {\n                            Log.i(TAG, \"got supporter:\" + supporter.id + \" name:\" + supporter.name);\n                            peerAppID = supporter.appid;\n                            peerUID = supporter.id;\n                            IMService.ConnectState state = IMService.getInstance().getConnectState();\n                            if (state == IMService.ConnectState.STATE_CONNECTED) {\n                                enableSend();\n                            }\n                        }\n                    }, new Action1<Throwable>() {\n                        @Override\n                        public void call(Throwable throwable) {\n                            Log.e(TAG, \"get supporter err:\" + throwable.getMessage());\n                        }\n                    });\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        Log.i(TAG, \"peer message activity destory\");\n\n        CustomerOutbox.getInstance().removeObserver(this);\n        IMService.getInstance().removeObserver(this);\n        IMService.getInstance().removeCustomerServiceObserver(this);\n        FileDownloader.getInstance().removeObserver(this);\n    }\n\n\n\n    @Override\n    public void onConnectState(IMService.ConnectState state) {\n        if (state == IMService.ConnectState.STATE_CONNECTED) {\n            if (peerAppID != 0 && peerUID != 0) {\n                enableSend();\n            }\n        } else {\n            disableSend();\n        }\n    }\n\n\n\n    @Override\n    public void onCustomerMessage(CustomerMessage msg) {\n        Log.i(TAG, \"recv msg:\" + msg.content);\n        final ICustomerMessage imsg = new ICustomerMessage();\n        imsg.timestamp = msg.timestamp;\n        imsg.msgLocalID = msg.msgLocalID;\n\n        imsg.senderAppID = msg.senderAppID;\n        imsg.sender = msg.sender;\n        imsg.receiverAppID = msg.receiverAppID;\n        imsg.receiver = msg.receiver;\n        if (msg.senderAppID == appid && msg.sender == this.currentUID) {\n            imsg.isOutgoing = true;\n            imsg.flags |= MessageFlag.MESSAGE_FLAG_ACK;\n        } else {\n            imsg.isOutgoing = false;\n        }\n        imsg.setContent(msg.content);\n\n        long msgPeerAppID;\n        long msgPeer;\n\n        if (imsg.isOutgoing) {\n            msgPeerAppID = msg.receiverAppID;\n            msgPeer = msg.receiver;\n        } else {\n            msgPeerAppID = msg.senderAppID;\n            msgPeer = msg.sender;\n        }\n\n        if (peerAppID != 0 && peerUID != 0) {\n            if (msgPeerAppID != peerAppID || msgPeer != peerUID) {\n                return;\n            }\n        }\n\n        if (storeID != 0 && imsg.getStoreId() != storeID) {\n            return;\n        }\n\n        IMessage mm = findMessage(imsg.getUUID());\n        if (mm != null) {\n            Log.i(TAG, \"receive repeat message:\" + imsg.getUUID());\n            if (imsg.isOutgoing) {\n                int flags = imsg.flags;\n                flags = flags & ~MessageFlag.MESSAGE_FLAG_FAILURE;\n                flags = flags | MessageFlag.MESSAGE_FLAG_ACK;\n                mm.setFlags(flags);\n            }\n            return;\n        }\n        if (msg.isSelf) {\n            return;\n        }\n\n        loadUserName(imsg);\n        downloadMessageContent(imsg);\n        updateNotificationDesc(imsg);\n        if (imsg.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n            Revoke revoke = (Revoke)imsg.content;\n            IMessage m = findMessage(revoke.msgid);\n            if (m != null) {\n                deleteMessage(m);\n            }\n        }\n        insertMessage(imsg);\n    }\n\n    @Override\n    public void onCustomerMessageACK(CustomerMessage msg) {\n\n        Log.i(TAG, \"customer service message ack\");\n\n        if (msg.msgLocalID > 0) {\n            IMessage imsg = findMessage(msg.msgLocalID);\n            if (imsg == null) {\n                Log.i(TAG, \"can't find msg:\" + msg.msgLocalID);\n                return;\n            }\n            imsg.setAck(true);\n        } else {\n            MessageContent c = IMessage.fromRaw(msg.content);\n            if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                Revoke r = (Revoke)c;\n                IMessage imsg = findMessage(r.msgid);\n                if (imsg == null) {\n                    Log.i(TAG, \"can't find msg:\" + r.msgid);\n                    return;\n                }\n                imsg.setContent(r);\n                updateNotificationDesc(imsg);\n                adapter.notifyDataSetChanged();\n            }\n        }\n    }\n\n    @Override\n    public void onCustomerMessageFailure(CustomerMessage msg) {\n        Log.i(TAG, \"message failure\");\n\n        if (msg.msgLocalID > 0) {\n            IMessage imsg = findMessage(msg.msgLocalID);\n            if (imsg == null) {\n                Log.i(TAG, \"can't find msg:\" + msg.msgLocalID);\n                return;\n            }\n            imsg.setFailure(true);\n        } else {\n            MessageContent c = IMessage.fromRaw(msg.content);\n            if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                Toast.makeText(this, \"撤回失败\", Toast.LENGTH_SHORT).show();\n            }\n        }\n    }\n\n    @Override\n    protected MessageIterator createMessageIterator() {\n        CustomerMessageDB db = (CustomerMessageDB) messageDB;\n        MessageIterator iter = db.newMessageIterator(storeID);\n        return iter;\n    }\n\n    @Override\n    protected MessageIterator createForwardMessageIterator(long messageID) {\n        CustomerMessageDB db = (CustomerMessageDB) messageDB;\n        MessageIterator iter = db.newForwardMessageIterator(storeID, messageID);\n        return iter;\n    }\n\n    @Override\n    protected MessageIterator createBackwardMessageIterator(long messageID) {\n        CustomerMessageDB db = (CustomerMessageDB) messageDB;\n        MessageIterator iter = db.newBackwardMessageIterator(storeID, messageID);\n        return iter;\n    }\n\n    @Override\n    protected MessageIterator createMiddleMessageIterator(long messageID) {\n        CustomerMessageDB db = (CustomerMessageDB) messageDB;\n        MessageIterator iter = db.newMiddleMessageIterator(storeID, messageID);\n        return iter;\n    }\n\n\n    @Override\n    protected boolean getMessageOutgoing(IMessage msg) {\n        ICustomerMessage m = (ICustomerMessage)msg;\n        return (msg.sender == currentUID && m.senderAppID == appid);\n    }\n\n    @Override\n    protected void sendMessage(IMessage imsg) {\n        CustomerOutbox.getInstance().sendMessage(imsg);\n    }\n\n    @Override\n    protected IMessage newOutMessage(MessageContent content) {\n        ICustomerMessage msg = new ICustomerMessage();\n        msg.senderAppID = appid;\n        msg.sender = currentUID;\n        msg.receiverAppID = peerAppID;\n        msg.receiver = peerUID;\n\n        content.generateRaw(storeID, sessionID, storeName, name, appName);\n        msg.content = content;\n        return msg;\n    }\n\n\n    @Override\n    protected void loadUserName(IMessage msg) {\n        if (!(msg instanceof ICustomerMessage)) {\n            return;\n        }\n\n        User u = new User();\n        ICustomerMessage cm = (ICustomerMessage) msg;\n        u.name = msg.content.getName();\n        u.identifier = String.format(\"%d\", msg.sender);\n        u.avatarURL = \"\";\n\n        msg.setSenderAvatar(u.avatarURL);\n        if (TextUtils.isEmpty(u.name)) {\n            msg.setSenderName(u.identifier);\n        } else {\n            msg.setSenderName(u.name);\n        }\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/GroupMessageActivity.java",
    "content": "package com.beetle.bauhinia;\n\nimport android.content.Intent;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.widget.Toast;\n\nimport com.beetle.bauhinia.db.GroupMessageDB;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.MessageFlag;\nimport com.beetle.bauhinia.db.MessageIterator;\nimport com.beetle.bauhinia.db.message.GroupNotification;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Revoke;\nimport com.beetle.bauhinia.tools.FileDownloader;\nimport com.beetle.bauhinia.outbox.GroupOutbox;\nimport com.beetle.im.GroupMessageObserver;\nimport com.beetle.im.IMMessage;\nimport com.beetle.im.IMService;\nimport com.beetle.im.IMServiceObserver;\nimport com.beetle.im.MessageACK;\n\nimport java.util.List;\n\n\n/**\n * Created by houxh on 15/3/21.\n */\npublic class GroupMessageActivity extends MessageActivity implements\n        IMServiceObserver, GroupMessageObserver {\n    protected long currentUID;\n    protected long groupID;\n    protected String groupName;\n\n    public GroupMessageActivity() {\n        super();\n        isShowUserName = true;\n        isShowReaded = false;\n        isShowReply = false;\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        items[ITEM_VIDEO_CALL_ID] = false;\n        super.onCreate(savedInstanceState);\n\n        Intent intent = getIntent();\n\n        currentUID = intent.getLongExtra(\"current_uid\", 0);\n        if (currentUID == 0) {\n            Log.e(TAG, \"current uid is 0\");\n            return;\n        }\n        groupID = intent.getLongExtra(\"group_id\", 0);\n        if (groupID == 0) {\n            Log.e(TAG, \"peer uid is 0\");\n            return;\n        }\n        groupName = intent.getStringExtra(\"group_name\");\n        if (groupName == null) {\n            Log.e(TAG, \"peer name is null\");\n            return;\n        }\n\n        messageID = intent.getIntExtra(\"message_id\", 0);\n\n        getSupportActionBar().setTitle(groupName);\n\n        this.messageDB = GroupMessageDB.getInstance();\n\n        this.hasLateMore = this.messageID > 0;\n        this.hasEarlierMore = true;\n        this.loadData();\n\n        if (this.messages.size() > 0) {\n            if (messageID > 0) {\n                int index = -1;\n                for (int i = 0; i < this.messages.size(); i++) {\n                    if (messageID == this.messages.get(i).msgLocalID) {\n                        index = i;\n                        break;\n                    }\n                }\n\n                if (index != -1) {\n                    listview.setSelection(index);\n                }\n            } else {\n                //显示最后一条消息\n                listview.setSelection(this.messages.size() - 1);\n            }\n        }\n\n        GroupOutbox.getInstance().addObserver(this);\n        IMService.getInstance().addObserver(this);\n        IMService.getInstance().addGroupObserver(this);\n        FileDownloader.getInstance().addObserver(this);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        Log.i(TAG, \"peer message activity destory\");\n\n        GroupOutbox.getInstance().removeObserver(this);\n        IMService.getInstance().removeObserver(this);\n        IMService.getInstance().removeGroupObserver(this);\n        FileDownloader.getInstance().removeObserver(this);\n    }\n\n\n    @Override\n    public void onConnectState(IMService.ConnectState state) {\n        if (state == IMService.ConnectState.STATE_CONNECTED) {\n            enableSend();\n        } else {\n            disableSend();\n        }\n    }\n\n    @Override\n    public void onGroupMessages(List<IMMessage> msgs) {\n        for (IMMessage msg : msgs) {\n            if (msg.isGroupNotification) {\n                assert(msg.sender == 0);\n                this.onGroupNotification(msg.content);\n            } else {\n                this.onGroupMessage(msg);\n            }\n        }\n\n    }\n\n    public void onGroupMessage(IMMessage msg) {\n        if (msg.receiver != groupID) {\n            return;\n        }\n        Log.i(TAG, \"recv msg:\" + msg.content);\n        final IMessage imsg = new IMessage();\n        imsg.timestamp = msg.timestamp;\n        imsg.msgLocalID = msg.msgLocalID;\n        imsg.sender = msg.sender;\n        imsg.receiver = msg.receiver;\n        imsg.setContent(msg.content);\n        imsg.isOutgoing = (msg.sender == this.currentUID);\n        if (imsg.isOutgoing) {\n            imsg.flags |= MessageFlag.MESSAGE_FLAG_ACK;\n        }\n\n        IMessage mm = findMessage(imsg.getUUID());\n        if (mm != null) {\n            Log.i(TAG, \"receive repeat message:\" + imsg.getUUID());\n            if (imsg.isOutgoing) {\n                int flags = imsg.flags;\n                flags = flags & ~MessageFlag.MESSAGE_FLAG_FAILURE;\n                flags = flags | MessageFlag.MESSAGE_FLAG_ACK;\n                mm.setFlags(flags);\n            }\n            return;\n        }\n\n        if (msg.isSelf) {\n            return;\n        }\n\n        loadUserName(imsg);\n\n        downloadMessageContent(imsg);\n        updateNotificationDesc(imsg);\n        if (imsg.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n            Revoke revoke = (Revoke)imsg.content;\n            IMessage m = findMessage(revoke.msgid);\n            if (m != null) {\n                replaceMessage(m, imsg);\n            }\n        } else {\n            insertMessage(imsg);\n        }\n    }\n\n    @Override\n    public void onGroupMessageACK(IMMessage im, int error) {\n        long msgLocalID = im.msgLocalID;\n        long gid = im.receiver;\n        if (gid != groupID) {\n            return;\n        }\n        Log.i(TAG, \"message ack\");\n\n        if (error == MessageACK.MESSAGE_ACK_SUCCESS) {\n            if (msgLocalID > 0) {\n                IMessage imsg = findMessage(msgLocalID);\n                if (imsg == null) {\n                    Log.i(TAG, \"can't find msg:\" + msgLocalID);\n                    return;\n                }\n                imsg.setAck(true);\n            } else {\n                MessageContent c = IMessage.fromRaw(im.content);\n                if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                    Revoke r = (Revoke) c;\n                    IMessage imsg = findMessage(r.msgid);\n                    if (imsg == null) {\n                        Log.i(TAG, \"can't find msg:\" + msgLocalID);\n                        return;\n                    }\n                    imsg.setContent(r);\n                    updateNotificationDesc(imsg);\n                    adapter.notifyDataSetChanged();\n                }\n            }\n        } else {\n            if (msgLocalID > 0) {\n                IMessage imsg = findMessage(msgLocalID);\n                if (imsg == null) {\n                    Log.i(TAG, \"can't find msg:\" + msgLocalID);\n                    return;\n                }\n                imsg.setFailure(true);\n            } else {\n                MessageContent c = IMessage.fromRaw(im.content);\n                if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                    Toast.makeText(this, \"撤回失败\", Toast.LENGTH_SHORT).show();\n                }\n            }\n        }\n    }\n\n    @Override\n    public void onGroupMessageFailure(IMMessage im) {\n        long msgLocalID = im.msgLocalID;\n        long gid = im.receiver;\n        if (gid != groupID) {\n            return;\n        }\n        Log.i(TAG, \"message failure\");\n\n        if (msgLocalID > 0) {\n            IMessage imsg = findMessage(msgLocalID);\n            if (imsg == null) {\n                Log.i(TAG, \"can't find msg:\" + msgLocalID);\n                return;\n            }\n            imsg.setFailure(true);\n        } else {\n            MessageContent c = IMessage.fromRaw(im.content);\n            if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                Toast.makeText(this, \"撤回失败\", Toast.LENGTH_SHORT).show();\n            }\n        }\n    }\n\n    public void onGroupNotification(String text) {\n        GroupNotification notification = GroupNotification.newGroupNotification(text);\n\n        if (notification.groupID != groupID) {\n            return;\n        }\n\n        IMessage imsg = new IMessage();\n        imsg.sender = 0;\n        imsg.receiver = groupID;\n        imsg.timestamp = notification.timestamp;\n        imsg.setContent(notification);\n\n        updateNotificationDesc(imsg);\n\n        if (notification.notificationType == GroupNotification.NOTIFICATION_GROUP_NAME_UPDATED) {\n            this.groupName = notification.groupName;\n            getSupportActionBar().setTitle(groupName);\n        }\n        insertMessage(imsg);\n    }\n\n    @Override\n    protected User getUser(long uid) {\n        User u = new User();\n        u.uid = uid;\n        u.name = null;\n        u.avatarURL = \"\";\n        u.identifier = String.format(\"name:%d\", uid);\n        return u;\n    }\n\n    @Override\n    protected void asyncGetUser(long uid, GetUserCallback cb) {\n        final long fuid = uid;\n        final GetUserCallback fcb = cb;\n        new AsyncTask<Void, Integer, User>() {\n            @Override\n            protected User doInBackground(Void... urls) {\n                User u = new User();\n                u.uid = fuid;\n                u.name = String.format(\"name:%d\", fuid);\n                u.avatarURL = \"\";\n                u.identifier = String.format(\"name:%d\", fuid);\n                return u;\n            }\n            @Override\n            protected void onPostExecute(User result) {\n                fcb.onUser(result);\n            }\n        }.execute();\n    }\n\n\n    @Override\n    protected MessageIterator createMessageIterator() {\n        MessageIterator iter = GroupMessageDB.getInstance().newMessageIterator(groupID);\n        return iter;\n    }\n\n    @Override\n    protected MessageIterator createForwardMessageIterator(long messageID) {\n        MessageIterator iter = GroupMessageDB.getInstance().newForwardMessageIterator(groupID, messageID);\n        return iter;\n    }\n\n    @Override\n    protected MessageIterator createBackwardMessageIterator(long messageID) {\n        MessageIterator iter = GroupMessageDB.getInstance().newBackwardMessageIterator(groupID, messageID);\n        return iter;\n    }\n\n    @Override\n    protected MessageIterator createMiddleMessageIterator(long messageID) {\n        MessageIterator iter = GroupMessageDB.getInstance().newMiddleMessageIterator(groupID, messageID);\n        return iter;\n    }\n\n\n    @Override\n    protected boolean getMessageOutgoing(IMessage msg) {\n        return (msg.sender == currentUID);\n    }\n\n    @Override\n    protected void sendMessage(IMessage imsg) {\n        GroupOutbox.getInstance().sendMessage(imsg);\n    }\n\n\n    @Override\n    protected void clearConversation() {\n        super.clearConversation();\n        GroupMessageDB db = GroupMessageDB.getInstance();\n        db.clearConversation(this.groupID);\n    }\n\n    @Override\n    protected IMessage newOutMessage(MessageContent content) {\n        IMessage msg = new IMessage();\n        msg.sender = currentUID;\n        msg.receiver = groupID;\n        msg.receiverCount = 0;\n        msg.content = content;\n        return msg;\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/MessageActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia;\n\nimport android.Manifest;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.media.MediaPlayer;\nimport android.net.Uri;\nimport android.os.*;\n\nimport androidx.swiperefreshlayout.widget.SwipeRefreshLayout;\nimport android.text.ClipboardManager;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.GestureDetector;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.WindowManager;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.*;\n\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport com.beetle.bauhinia.activity.*;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.*;\nimport com.beetle.bauhinia.db.message.Notification;\nimport com.beetle.bauhinia.gallery.GalleryImage;\nimport com.beetle.bauhinia.gallery.ui.GalleryUI;\nimport com.beetle.bauhinia.outbox.OutboxObserver;\nimport com.beetle.bauhinia.toolbar.EaseChatExtendMenu;\nimport com.beetle.bauhinia.toolbar.EaseChatInputMenu;\nimport com.beetle.bauhinia.tools.*;\nimport com.beetle.bauhinia.view.*;\nimport com.beetle.im.IMMessage;\nimport com.beetle.im.IMService;\nimport com.beetle.imkit.BuildConfig;\n\nimport com.beetle.bauhinia.ChatItemQuickAction.ChatQuickAction;\nimport com.beetle.imkit.R;\n\n\nabstract public class MessageActivity extends MessageAudioActivity implements\n        SwipeRefreshLayout.OnRefreshListener {\n\n    protected static final String TAG = \"imservice\";\n\n\n    //permission request code\n    private static final int PERMISSIONS_REQUEST = 2;\n    private static final int CAMERA_PERMISSIONS_REQUEST = 3;//拍摄\n    private static final int LOCATION_PERMISSIONS_REQUEST = 4;//位置\n    private static final int PHOTO_PERMISSIONS_REQUEST = 5;//图片\n    private static final int VOICE_PERMISSIONS_REQUEST = 6;//语音\n\n\n    //activity request code\n    public static final int SELECT_PICTURE = 101;\n    public static final int SELECT_PICTURE_KITKAT = 102;\n    public static final int TAKE_PICTURE = 103;\n    public static final int PICK_LOCATION = 104;\n    public static final int CAPTURE_CAMERA = 105;\n    public static final int SELECT_FILE = 106;\n\n    private static final int IN_MSG = 0;\n    private static final int OUT_MSG = 1;\n\n    protected boolean isShowUserName = false;\n    protected boolean isShowReply = true;//显示回复信息标志\n    protected boolean isShowReaded = true;//显示未读/已读\n\n    protected BaseAdapter adapter;\n    protected ListView listview;\n    protected SwipeRefreshLayout swipeRefresh;\n    \n    static final int ITEM_TAKE_PICTURE = 1;\n    static final int ITEM_PICTURE = 2;\n    static final int ITEM_LOCATION = 3;\n    static final int ITEM_VIDEO_CALL = 4;\n    static final int ITEM_FILE = 5;\n\n\n    protected static final int ITEM_TAKE_PICTURE_ID = 0;\n    protected static final int ITEM_PICTURE_ID = 1;\n    protected static final int ITEM_LOCATION_ID = 2;\n    protected static final int ITEM_VIDEO_CALL_ID = 3;\n    protected static final int ITEM_FILE_ID = 4;\n\n    protected int[] itemStrings = { R.string.attach_take_pic, R.string.attach_picture,\n            R.string.attach_location, R.string.attach_video_call, R.string.attach_file };\n    protected int[] itemdrawables = { R.drawable.ease_chat_takepic_selector, R.drawable.ease_chat_image_selector,\n            R.drawable.ease_chat_location_selector,  R.drawable.ease_chat_video_call_selector, R.drawable.ease_chat_file_selector};\n    protected int[] itemIds = { ITEM_TAKE_PICTURE, ITEM_PICTURE, ITEM_LOCATION, ITEM_VIDEO_CALL, ITEM_FILE };\n\n    protected boolean[] items = {true, true, true, true, true};\n\n\n    protected MenuItemClickListener extendMenuItemClickListener;\n    protected EaseChatInputMenu inputMenu;\n\n    /**\n     * 扩展菜单栏item点击事件\n     *\n     */\n    class MenuItemClickListener implements EaseChatExtendMenu.EaseChatExtendMenuItemClickListener{\n        @Override\n        public void onClick(int itemId, View view) {\n            switch (itemId) {\n                case ITEM_TAKE_PICTURE: // 拍照\n                    takePicture();\n                    break;\n                case ITEM_PICTURE:\n                    getPicture(); // 图库选择图片\n                    break;\n                case ITEM_LOCATION: // 位置\n                    if (checkLocationPermission()) {\n                        startActivityForResult(new Intent(MessageActivity.this, LocationPickerActivity.class), PICK_LOCATION);\n                    } else {\n                        requestLocationPermission();\n                    }\n                    break;\n                case ITEM_VIDEO_CALL:\n                    call();\n                    break;\n                case ITEM_FILE:\n                    getFile();\n                    break;\n                default:\n                    break;\n            }\n        }\n    }\n\n\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.chat);\n\n        File f = new File(getCacheDir(), \"bh_audio.amr\");\n        recordFileName = f.getAbsolutePath();\n        Log.i(TAG, \"record file name:\" + recordFileName);\n\n        extendMenuItemClickListener = new MenuItemClickListener();\n        inputMenu = (EaseChatInputMenu)findViewById(R.id.input_menu);\n        registerExtendMenuItem();\n        inputMenu.init();\n        inputMenu.setChatInputMenuListener(new EaseChatInputMenu.ChatInputMenuListener() {\n\n            @Override\n            public void onSendMessage(String content, List<Long> at, List<String> atNames) {\n                // 发送文本消息\n                sendTextMessage(content, at, atNames);\n            }\n\n            @Override\n            public void onFocusChanged(boolean hasFocus) {\n                if (hasFocus) {\n                    onInputFocusChanged(hasFocus);\n                }\n            }\n\n            @Override\n            public boolean onPressToSpeakBtnTouch(View v, MotionEvent event) {\n                switch (event.getAction()) {\n                    case MotionEvent.ACTION_DOWN:\n                        try {\n                            if (!checkRecordPermission()) {\n                                //用户需要再次操作\n                                v.setPressed(false);\n                                requestRecordPermission();\n                            } else {\n                                v.setPressed(true);\n                                MessageActivity.this.startRecord();\n                            }\n                        } catch (Exception e) {\n                            v.setPressed(false);\n                        }\n                        return true;\n                    case MotionEvent.ACTION_MOVE:\n                        if (event.getY() < 0) {\n                             MessageActivity.this.showReleaseToCancelHint();\n                        } else {\n                             MessageActivity.this.showMoveUpToCancelHint();\n                        }\n                        return true;\n                    case MotionEvent.ACTION_UP:\n                        v.setPressed(false);\n                        if (event.getY() < 0) {\n                            // discard the recorded audio.\n                            MessageActivity.this.discardRecord();\n                        } else {\n                            // stop recording and send voice file\n                            MessageActivity.this.stopRecord();\n                        }\n                        return true;\n                    default:\n                        MessageActivity.this.discardRecord();\n                        return false;\n                }\n            }\n            @Override\n            public void onAt() {\n                MessageActivity.this.onAt();\n            }\n        });\n\n        SwipeRefreshLayout swipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);\n        swipeLayout.setOnRefreshListener(this);\n        swipeRefresh = swipeLayout;\n\n        listview = (ListView)findViewById(R.id.list_view);\n        adapter = new ChatAdapter();\n        listview.setAdapter(adapter);\n\n        listview.setOnTouchListener(new View.OnTouchListener() {\n            @Override\n            public boolean onTouch(View v, MotionEvent event) {\n                //hide keyboard\n                if (getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) {\n                    InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);\n                    if (getCurrentFocus() != null) {\n                        inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);\n                    }\n                }\n                inputMenu.hideExtendMenuContainer();\n                inputMenu.clearFocus();\n                return false;\n            }\n        });\n\n        listview.setOnScrollListener(new AbsListView.OnScrollListener() {\n            private boolean reachBottom = false;\n\n            @Override\n            public void onScrollStateChanged(AbsListView view, int scrollState) {\n                if (scrollState == SCROLL_STATE_IDLE && reachBottom) {\n                    reachBottom = false;\n                    int count = loadLaterData();\n                    if (count > 0) {\n                        adapter.notifyDataSetChanged();\n                    }\n                }\n            }\n\n            @Override\n            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {\n                int visibleThreshold = 2;\n                if (totalItemCount <= (firstVisibleItem + visibleItemCount + visibleThreshold)) {\n                    reachBottom = true;\n                }\n            }\n        });\n\n        audioUtil = new AudioUtil(this);\n        audioUtil.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {\n            @Override\n            public void onCompletion(MediaPlayer mediaPlayer) {\n                if (MessageActivity.this.playingMessage != null) {\n                    MessageActivity.this.playingMessage.setPlaying(false);\n                    MessageActivity.this.playingMessage = null;\n                }\n            }\n        });\n        audioUtil.setOnStopListener(new AudioUtil.OnStopListener() {\n            @Override\n            public void onStop(int reason) {\n                adapter.notifyDataSetChanged();\n            }\n        });\n\n        audioRecorder = new AudioRecorder(this, this.recordFileName);\n\n        if (IMService.getInstance().getConnectState() != IMService.ConnectState.STATE_CONNECTED) {\n            disableSend();\n        }\n\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    public void onBackPressed() {\n        Log.i(TAG, \"on back pressed\");\n        //hide keyboard\n        if (getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) {\n            InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);\n            if (getCurrentFocus() != null) {\n                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);\n            }\n        }\n        inputMenu.hideExtendMenuContainer();\n        inputMenu.clearFocus();\n\n        super.onBackPressed();\n    }\n\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        Log.i(TAG, \"imactivity destory\");\n        audioUtil.release();\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n\n\n        boolean granted = true;\n        for (int i = 0; i < grantResults.length; i++) {\n            if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {\n                granted = false;\n            }\n        }\n\n        if (!granted) {\n            return;\n        }\n\n        if (requestCode == CAMERA_PERMISSIONS_REQUEST) {\n            Intent intent = new Intent(this, CameraActivity.class);\n            intent.putExtra(\"dir\", getCacheDir().getAbsolutePath());\n            startActivityForResult(intent, CAPTURE_CAMERA);\n        } else if (requestCode == LOCATION_PERMISSIONS_REQUEST) {\n            startActivityForResult(new Intent(MessageActivity.this, LocationPickerActivity.class), PICK_LOCATION);\n        } else if (requestCode == PHOTO_PERMISSIONS_REQUEST) {\n            getPicture();\n        }\n    }\n\n    private boolean checkRecordPermission() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            int recordPermission = (checkSelfPermission(Manifest.permission.RECORD_AUDIO));\n            return recordPermission == PackageManager.PERMISSION_GRANTED;\n        }\n        return true;\n    }\n\n    private boolean checkPhotoPermission() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            int readExternalPermission = (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE));\n            return readExternalPermission == PackageManager.PERMISSION_GRANTED;\n        }\n        return true;\n    }\n\n    private boolean checkLocationPermission() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            int fineLocationPermission = (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION));\n            int coarseLocationPermission = (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION));\n            return fineLocationPermission == PackageManager.PERMISSION_GRANTED && coarseLocationPermission == PackageManager.PERMISSION_GRANTED;\n        }\n\n        return true;\n    }\n\n    private boolean checkCameraPermission() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            int recordPermission = (checkSelfPermission(Manifest.permission.RECORD_AUDIO));\n            int cameraPermission = (checkSelfPermission(Manifest.permission.CAMERA));\n            return recordPermission == PackageManager.PERMISSION_GRANTED && cameraPermission == PackageManager.PERMISSION_GRANTED;\n        }\n        return true;\n    }\n\n    private void requestRecordPermission() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            String[] array = {Manifest.permission.RECORD_AUDIO};\n            this.requestPermissions(array, VOICE_PERMISSIONS_REQUEST);\n        }\n    }\n\n\n    private void requestPhotoPermission() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            String[] array = {Manifest.permission.READ_EXTERNAL_STORAGE};\n            this.requestPermissions(array, PHOTO_PERMISSIONS_REQUEST);\n        }\n    }\n\n    private void requestLocationPermission() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            String[] array = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};\n            this.requestPermissions(array, LOCATION_PERMISSIONS_REQUEST);\n        }\n    }\n\n\n    private void requestCameraPermission() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            String[] array = {Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA};\n            this.requestPermissions(array, CAMERA_PERMISSIONS_REQUEST);\n        }\n    }\n\n    /**\n     * 注册底部菜单扩展栏item; 覆盖此方法时如果不覆盖已有item，item的id需大于3\n     */\n    protected void registerExtendMenuItem(){\n        for(int i = 0; i < itemStrings.length; i++){\n            if (items[i]) {\n                inputMenu.registerExtendMenuItem(itemStrings[i], itemdrawables[i], itemIds[i], extendMenuItemClickListener);\n            }\n        }\n    }\n\n    static interface ContentTypes {\n        public static int UNKNOWN = 0;\n        public static int AUDIO = 2;\n        public static int IMAGE = 4;\n        public static int LOCATION = 6;\n        public static int TEXT = 8;\n        public static int NOTIFICATION = 10;\n        public static int LINK = 12;\n        public static int VOIP = 14;\n        public static int FILE = 16;\n        public static int VIDEO = 18;\n        public static int CLASSROOM = 20;\n        public static int CONFERENCE = 22;\n\n    }\n    private static int VIEW_TYPE_COUNT = 24;\n\n    class ChatAdapter extends BaseAdapter implements ContentTypes {\n        @Override\n        public int getCount() {\n            return messages.size();\n        }\n        @Override\n        public Object getItem(int position) {\n            return messages.get(position);\n        }\n        @Override\n        public long getItemId(int position) {\n            return position;\n        }\n\n        @Override\n        public int getItemViewType(int position) {\n            final int basic;\n            if (messages.get(position).isOutgoing) {\n                basic = OUT_MSG;\n            } else {\n                basic = IN_MSG;\n            }\n            return getMediaType(position) + basic;\n        }\n\n        int getMediaType(int position) {\n            IMessage msg = messages.get(position);\n            final int media;\n            if (msg.content instanceof Text) {\n                media = TEXT;\n            } else if (msg.content instanceof Image) {\n                media = IMAGE;\n            } else if (msg.content instanceof Audio) {\n                media = AUDIO;\n            } else if (msg.content instanceof Location) {\n                media = LOCATION;\n            } else if (msg.content instanceof Notification) {\n                media = NOTIFICATION;\n            } else if (msg.content instanceof Link) {\n                media = LINK;\n            } else if (msg.content instanceof VOIP) {\n                media = VOIP;\n            } else if (msg.content instanceof com.beetle.bauhinia.db.message.File) {\n                media = FILE;\n            } else if (msg.content instanceof Video) {\n                media = VIDEO;\n            } else if (msg.content instanceof Classroom) {\n                media = CLASSROOM;\n            } else if (msg.content instanceof Conference) {\n                media = CONFERENCE;\n            } else {\n                media = UNKNOWN;\n            }\n\n            return media;\n        }\n\n        @Override\n        public int getViewTypeCount() {\n            return VIEW_TYPE_COUNT;\n        }\n\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n            IMessage msg = messages.get(position);\n            MessageRowView rowView = (MessageRowView)convertView;\n            if (rowView == null) {\n                MessageContent.MessageType msgType = msg.content.getType();\n                if (msg.content instanceof Notification) {\n                    rowView = new MiddleMessageView(MessageActivity.this, msgType);\n                } else  if (msg.isOutgoing) {\n                    OutMessageView msgView = new OutMessageView(MessageActivity.this, msgType, isShowReply, isShowReaded);\n                    msgView.readedButton.setOnClickListener(new View.OnClickListener() {\n                        @Override\n                        public void onClick(View v) {\n                            IMessage im = (IMessage)v.getTag();\n                            Log.i(TAG, \"im:\" + im.msgLocalID);\n                            MessageActivity.this.openUnread(im);\n                        }\n                    });\n                    rowView = msgView;\n\n                } else {\n                    InMessageView msgView = new InMessageView(MessageActivity.this, msgType, isShowUserName, isShowReply);\n                    rowView = msgView;\n                }\n                if (rowView != null) {\n                    if (rowView.getContentFrame() != null) {\n                        final View contentFrame = rowView.getContentFrame();\n                        final GestureDetector detector = new GestureDetector(MessageActivity.this, new GestureDetector.SimpleOnGestureListener() {\n                            @Override\n                            public boolean onDown(MotionEvent e) {\n                                //return true for MotionEvent.ACTION_UP\n                                return true;\n                            }\n\n                            @Override\n                            public boolean onDoubleTap(MotionEvent e) {\n                                IMessage im = (IMessage)contentFrame.getTag();\n                                if (im.getType() == MessageContent.MessageType.MESSAGE_TEXT) {\n                                    Text t = (Text) im.content;\n                                    Log.i(TAG, \"double click:\" + t.text);\n                                    Intent intent = new Intent();\n                                    intent.setClass(MessageActivity.this, OverlayActivity.class);\n                                    intent.putExtra(\"text\", t.text);\n                                    MessageActivity.this.startActivity(intent);\n                                }\n                                return true;\n                            }\n\n                            @Override\n                            public boolean onSingleTapConfirmed(MotionEvent e) {\n                                IMessage im = (IMessage)contentFrame.getTag();\n                                Log.i(TAG, \"im:\" + im.msgLocalID);\n                                MessageActivity.this.onMessageClicked(im);\n                                return true;\n                            }\n\n                            @Override\n                            public void onLongPress(MotionEvent e) {\n                                final IMessage im = (IMessage)contentFrame.getTag();\n                                ArrayList<ChatQuickAction> actions = getLongClickActions(im);\n                                if (actions.size() == 0) {\n                                    return;\n                                }\n\n                                ChatItemQuickAction.showAction(MessageActivity.this,\n                                        actions.toArray(new ChatQuickAction[actions.size()]),\n                                        new ChatItemQuickAction.ChatQuickActionResult() {\n                                            @Override\n                                            public void onSelect(ChatQuickAction action) {\n                                                onActionClickListener(action, im);\n                                            }\n                                        }\n                                );\n                            }\n                        });\n\n                        contentFrame.setOnTouchListener(new View.OnTouchListener() {\n                            @Override\n                            public boolean onTouch(View v, MotionEvent event) {\n                                return detector.onTouchEvent(event);\n                            }\n                        });\n                    }\n\n                    if (rowView.getReplyButton() != null) {\n                        Button replyBtn = rowView.getReplyButton();\n                        replyBtn.setOnClickListener(new View.OnClickListener() {\n                            @Override\n                            public void onClick(View v) {\n                                IMessage im = (IMessage)v.getTag();\n                                Log.i(TAG, \"im:\" + im.msgLocalID);\n                                MessageActivity.this.openReply(im);\n                            }\n                        });\n                    }\n                }\n            }\n            rowView.setMessage(msg);\n            return rowView;\n        }\n    }\n\n    protected void disableSend() {\n        inputMenu.disableSend();\n    }\n\n    protected void enableSend() {\n        inputMenu.enableSend();\n    }\n\n    public void onRefresh() {\n        final SwipeRefreshLayout swipeLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_container);\n        swipeLayout.setRefreshing(false);\n\n        int count = loadEarlierData();\n        if (count > 0) {\n            adapter.notifyDataSetChanged();\n            listview.setSelection(count);\n        }\n    }\n\n\n\n    protected void clearConversation() {\n        Log.i(TAG, \"clearConversation\");\n        messages = new ArrayList<IMessage>();\n        adapter.notifyDataSetChanged();\n    }\n\n\n    @Override\n    protected void insertMessage(IMessage imsg) {\n        super.insertMessage(imsg);\n        adapter.notifyDataSetChanged();\n        listview.smoothScrollToPosition(messages.size()-1);\n    }\n\n    @Override\n    protected void replaceMessage(IMessage imsg, IMessage other) {\n        super.replaceMessage(imsg, other);\n        adapter.notifyDataSetChanged();\n    }\n\n    protected void onInputFocusChanged(boolean hasFocus) {\n        if (hasFocus) {\n            if (hasLateMore) {\n                hasLateMore = false;\n                this.messageID = 0;\n                messages = new ArrayList<IMessage>();\n                loadData();\n                adapter.notifyDataSetChanged();\n                //scroll to bottom\n                if (messages.size() > 0) {\n                    listview.setSelection(messages.size() - 1);\n                }\n            } else {\n                listview.setSelection(messages.size() - 1);\n            }\n        }\n    }\n\n    void getPicture() {\n        if (!this.checkPhotoPermission()) {\n            requestPhotoPermission();\n            return;\n        }\n\n        if (Build.VERSION.SDK_INT <19){\n            Intent intent = new Intent();\n            intent.setType(\"image/*\");\n            intent.setAction(Intent.ACTION_GET_CONTENT);\n            startActivityForResult(Intent.createChooser(intent\n                    , getString(R.string.product_fotos_get_from))\n                    , SELECT_PICTURE);\n        } else {\n            Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);\n            startActivityForResult(intent, SELECT_PICTURE_KITKAT);\n        }\n    }\n\n    void takePicture() {\n        if (checkCameraPermission()) {\n            Intent intent = new Intent(this, CameraActivity.class);\n            intent.putExtra(\"dir\", getCacheDir().getAbsolutePath());\n            startActivityForResult(intent, CAPTURE_CAMERA);\n        } else {\n            requestCameraPermission();\n        }\n    }\n\n    void getFile() {\n        Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);\n        chooseFile.setType(\"*/*\");\n        chooseFile.addCategory(Intent.CATEGORY_OPENABLE);\n        chooseFile.putExtra(Intent.EXTRA_LOCAL_ONLY, true);\n        chooseFile = Intent.createChooser(chooseFile, \"选择文件\");\n        startActivityForResult(chooseFile, SELECT_FILE);\n    }\n\n    public void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        if (resultCode != RESULT_OK) {\n            Log.i(TAG, \"take or select picture fail:\" + resultCode);\n            return;\n        }\n\n        if (requestCode == SELECT_PICTURE || requestCode == SELECT_PICTURE_KITKAT) {\n            try {\n                Uri selectedImageUri = data.getData();\n                Log.i(TAG, \"selected image uri:\" + selectedImageUri);\n                InputStream is = getContentResolver().openInputStream(selectedImageUri);\n                BitmapFactory.Options options = new BitmapFactory.Options();\n                options.inPreferredConfig = Bitmap.Config.ARGB_8888;\n                Bitmap bmp = BitmapFactory.decodeStream(is, null, options);\n                sendImageMessage(bmp);\n            } catch (Exception e) {\n                e.printStackTrace();\n                return;\n            }\n        } else if (requestCode == PICK_LOCATION) {\n            float longitude = data.getFloatExtra(\"longitude\", 0);\n            float latitude = data.getFloatExtra(\"latitude\", 0);\n            String address = data.getStringExtra(\"address\");\n\n            Log.i(TAG, \"address:\" + address + \" longitude:\" + longitude + \" latitude:\" + latitude);\n            sendLocationMessage(longitude, latitude, address);\n        } else if (requestCode == CAPTURE_CAMERA) {\n            String videoPath = data.getStringExtra(\"video_path\");\n            String thumbPath = data.getStringExtra(\"thumbnail_path\");\n            String picturePath = data.getStringExtra(\"picture_path\");\n\n            if (!TextUtils.isEmpty(picturePath)) {\n                Log.i(TAG, \"take picture success:\" + picturePath);\n                BitmapFactory.Options options = new BitmapFactory.Options();\n                options.inPreferredConfig = Bitmap.Config.ARGB_8888;\n                Bitmap bmp = BitmapFactory.decodeFile(picturePath, options);\n                sendImageMessage(bmp);\n\n                //删除临时文件\n                new File(picturePath).delete();\n            } else if (!TextUtils.isEmpty(videoPath) && !TextUtils.isEmpty(thumbPath)) {\n                Log.i(TAG, \"take video success:\" + videoPath + \" thumbnail path:\" + thumbPath);\n                sendVideoMessage(videoPath, thumbPath);\n            }\n        } else if (requestCode == SELECT_FILE) {\n            Uri fileUri = data.getData();\n            sendFileMessage(fileUri);\n        } else {\n            Log.i(TAG, \"invalide request code:\" + requestCode);\n            return;\n        }\n    }\n\n\n\n    protected void onMessageClicked(IMessage message) {\n        if (message.content instanceof Audio) {\n            Audio audio = (Audio) message.content;\n            if (FileCache.getInstance().isCached(audio.url)) {\n                play(message);\n            } else {\n                FileDownloader.getInstance().download(message);\n            }\n        } else if (message.content instanceof Image) {\n            navigateToViewImage(message);\n        } else if (message.content.getType() == MessageContent.MessageType.MESSAGE_LOCATION) {\n            Log.i(TAG, \"location message clicked\");\n            Location loc = (Location)message.content;\n            startActivity(MapActivity.newIntent(this, loc.longitude, loc.latitude));\n        } else if (message.content.getType() == MessageContent.MessageType.MESSAGE_LINK) {\n            Link link = (Link)message.content;\n            Intent intent = new Intent();\n            intent.putExtra(\"url\", link.url);\n            intent.setClass(this, WebActivity.class);\n            startActivity(intent);\n        } else if (message.getType() == MessageContent.MessageType.MESSAGE_FILE) {\n            com.beetle.bauhinia.db.message.File f = (com.beetle.bauhinia.db.message.File)message.content;\n            Intent intent = new Intent();\n            intent.putExtra(\"url\", f.url);\n            intent.putExtra(\"size\", f.size);\n            intent.putExtra(\"filename\", f.filename);\n            intent.setClass(this, MessageFileActivity.class);\n            startActivity(intent);\n        } else if (message.getType() == MessageContent.MessageType.MESSAGE_VIDEO) {\n            Video v = (Video)message.content;\n            Intent intent = new Intent();\n            intent.putExtra(\"url\", v.url);\n            intent.putExtra(\"sender\", message.sender);\n            intent.putExtra(\"secret\", message.secret);\n            intent.setClass(this, PlayerActivity.class);\n            startActivity(intent);\n        }\n    }\n\n    private void navigateToViewImage(IMessage imageMessage) {\n        ArrayList<IMessage> imageMessages = getImageMessages();\n        if (imageMessages == null) {\n            return;\n        }\n\n        int position = 0;\n        ArrayList<GalleryImage> galleryImages = new ArrayList<GalleryImage>();\n        for (IMessage msg : imageMessages) {\n            Image image = (Image) msg.content;\n            if (msg.msgLocalID == imageMessage.msgLocalID) {\n                position = galleryImages.size();\n            }\n            galleryImages.add(new GalleryImage(image.url));\n        }\n        Intent intent = GalleryUI.getCallingIntent(this, galleryImages, position);\n        startActivity(intent);\n    }\n\n    protected void call() {}\n\n    protected void onAt() {}\n\n    protected void forward(IMessage im) {}\n\n    protected void openUnread(IMessage im) {}\n\n    protected void openReply(IMessage message) {\n\n    }\n\n    protected ArrayList<ChatQuickAction> getLongClickActions(IMessage im) {\n        ArrayList<ChatQuickAction> actions = new ArrayList<ChatQuickAction>();\n\n        if (im.content.getType() == MessageContent.MessageType.MESSAGE_TEXT) {\n            actions.add(ChatItemQuickAction.ChatQuickAction.COPY);\n        }\n\n        if (im.isFailure()) {\n            actions.add(ChatQuickAction.RESEND);\n        } else {\n            if (im.content.getType() == MessageContent.MessageType.MESSAGE_TEXT ||\n                    im.content.getType() == MessageContent.\n                            MessageType.MESSAGE_IMAGE ||\n                    im.content.getType() == MessageContent.\n                            MessageType.MESSAGE_AUDIO ||\n                    im.content.getType() == MessageContent.\n                            MessageType.MESSAGE_VIDEO ||\n                    im.content.getType() == MessageContent.\n                            MessageType.MESSAGE_LOCATION ||\n                    im.content.getType() == MessageContent.MessageType.MESSAGE_FILE) {\n                actions.add(ChatQuickAction.FORWARD);\n            }\n        }\n        int now = now();\n        if (now >= im.timestamp && (now - im.timestamp) < (REVOKE_EXPIRE-10) && im.isOutgoing) {\n            actions.add(ChatQuickAction.REVOKE);\n        }\n        return actions;\n    }\n\n    protected void onActionClickListener(ChatQuickAction action, IMessage im) {\n        switch (action) {\n            case COPY:\n                ClipboardManager clipboard =\n                        (ClipboardManager)MessageActivity.this\n                                .getSystemService(Context.CLIPBOARD_SERVICE);\n                clipboard.setText(((Text) im.content).text);\n                break;\n            case RESEND:\n                MessageActivity.this.resend(im);\n                break;\n            case REVOKE:\n                MessageActivity.this.revoke(im);\n                break;\n            case FORWARD:\n                MessageActivity.this.forward(im);\n            default:\n                break;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/MessageAudioActivity.java",
    "content": "package com.beetle.bauhinia;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.os.Handler;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.Audio;\nimport com.beetle.bauhinia.tools.AudioRecorder;\nimport com.beetle.bauhinia.tools.AudioUtil;\nimport com.beetle.bauhinia.tools.DeviceUtil;\nimport com.beetle.bauhinia.tools.FileCache;\nimport com.beetle.imkit.R;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Date;\nimport java.util.TimerTask;\n\nabstract public class MessageAudioActivity extends MessageBaseActivity {\n    IMessage playingMessage;\n\n    //录音相关\n    protected Handler mHandler = new Handler();\n    protected java.util.Timer sixtySecondsTimer;\n    protected java.util.Timer recordingTimer;\n    protected AlertDialog alertDialog;\n\n    protected ImageView recordingImageBG;\n\n    protected ImageView recordingImage;\n\n    protected TextView recordingText;\n\n    protected Date mBegin;\n\n    protected String recordFileName;\n\n    protected AudioRecorder audioRecorder;\n    protected AudioUtil audioUtil;\n\n\n    protected class VolumeTimerTask extends TimerTask {\n        @Override\n        public void run() {\n            mHandler.post(new Runnable() {\n                @Override\n                public void run() {\n                    MessageAudioActivity.this.refreshVolume();\n                }\n            });\n        }\n    }\n\n\n    protected void showReleaseToCancelHint() {\n        if (recordingText != null) {\n            recordingText.setText(getString(R.string.release_to_cancel));\n            recordingText.setBackgroundResource(R.drawable.ease_recording_text_hint_bg);\n        }\n    }\n\n    protected void showMoveUpToCancelHint() {\n        if (recordingText != null) {\n            recordingText.setText(getString(R.string.move_up_to_cancel));\n            recordingText.setBackgroundColor(Color.TRANSPARENT);\n        }\n    }\n\n    protected void showRecordDialog() {\n        AlertDialog.Builder builder;\n\n        LayoutInflater inflater = (LayoutInflater) this\n                .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);\n        View layout = inflater.inflate(\n                R.layout.conversation_recording_dialog,\n                (ViewGroup) findViewById(R.id.conversation_recording));\n\n        recordingImage = (ImageView) layout\n                .findViewById(R.id.conversation_recording_range);\n        recordingImageBG = (ImageView) layout\n                .findViewById(R.id.conversation_recording_white);\n\n        recordingText = (TextView) layout\n                .findViewById(R.id.conversation_recording_text);\n\n        showMoveUpToCancelHint();\n\n        builder = new AlertDialog.Builder(this);\n        alertDialog = builder.create();\n        alertDialog.show();\n        alertDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));\n        alertDialog.getWindow().setContentView(layout);\n    }\n\n    protected void refreshVolume() {\n        if (!this.audioRecorder.isRecording()) {\n            return;\n        }\n\n        int max = this.audioRecorder.getMaxAmplitude();\n\n        if (max != 0) {\n            FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) recordingImage\n                    .getLayoutParams();\n            float scale = max / 7000.0f;\n            if (scale < 0.3) {\n                recordingImage\n                        .setImageResource(R.drawable.record_red);\n            } else {\n                recordingImage\n                        .setImageResource(R.drawable.record_green);\n            }\n            if (scale > 1) {\n                scale = 1;\n            }\n            int height = recordingImageBG.getHeight()\n                    - (int) (scale * recordingImageBG.getHeight());\n            params.setMargins(0, 0, 0, -1 * height);\n            recordingImage.setLayoutParams(params);\n\n            ((View) recordingImage).scrollTo(0, height);\n            // Log.i(TAG, \"max amplitude: \" + max);\n            /**\n             * 倒计时提醒\n             */\n            Date now = new Date();\n            long between = (mBegin.getTime() + 60000)\n                    - now.getTime();\n            if (between < 10000) {\n                int second = (int) (Math.floor((between / 1000)));\n                if (second == 0) {\n                    second = 1;\n                }\n                recordingText.setText(\"还剩: \" + second + \"秒\");\n            }\n        }\n    }\n\n    protected void startRecord() {\n        if (audioUtil.isPlaying()) {\n            audioUtil.stopPlay();\n        }\n\n        mBegin = new Date();\n        //删除上次录音生成的文件内容\n        new File(recordFileName).delete();\n        audioRecorder.startRecord();\n        sixtySecondsTimer = new java.util.Timer();\n        sixtySecondsTimer.schedule(new TimerTask() {\n\n            @Override\n            public void run() {\n                mHandler.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        Log.i(TAG, \"recording end by timeout\");\n                        MessageAudioActivity.this.stopRecord();\n                    }\n                });\n            }\n        }, 60000);\n\n        recordingTimer = new java.util.Timer();\n        recordingTimer.schedule(new MessageAudioActivity.VolumeTimerTask(), 0, 100);\n\n        showRecordDialog();\n    }\n\n    protected void discardRecord() {\n        // stop sixty seconds limit\n        if (sixtySecondsTimer != null) {\n            sixtySecondsTimer.cancel();\n            sixtySecondsTimer = null;\n        }\n        // stop volume task\n        if (recordingTimer != null) {\n            recordingTimer.cancel();\n            recordingTimer = null;\n        }\n        if (alertDialog != null) {\n            alertDialog.dismiss();\n        }\n\n        if (MessageAudioActivity.this.audioRecorder.isRecording()) {\n            MessageAudioActivity.this.audioRecorder.stopRecord();\n        }\n    }\n\n    protected void stopRecord() {\n        // stop sixty seconds limit\n        if (sixtySecondsTimer != null) {\n            sixtySecondsTimer.cancel();\n            sixtySecondsTimer = null;\n        }\n        // stop volume task\n        if (recordingTimer != null) {\n            recordingTimer.cancel();\n            recordingTimer = null;\n        }\n        if (alertDialog != null) {\n            alertDialog.dismiss();\n        }\n\n        if (MessageAudioActivity.this.audioRecorder.isRecording()) {\n            MessageAudioActivity.this.audioRecorder.stopRecord();\n            String tfile = audioRecorder.getPathName();\n            MessageAudioActivity.this.sendAudioMessage(tfile);\n        }\n    }\n\n\n\n    protected void play(IMessage message) {\n        Audio audio = (Audio) message.content;\n        Log.i(TAG, \"url:\" + audio.url);\n        if (FileCache.getInstance().isCached(audio.url)) {\n            try {\n                if (audioRecorder.isRecording()) {\n                    audioRecorder.stopRecord();\n                }\n                if (playingMessage != null && playingMessage == message) {\n                    //停止播放\n                    audioUtil.stopPlay();\n                    playingMessage.setPlaying(false);\n                    playingMessage = null;\n                } else {\n                    if (playingMessage != null) {\n                        audioUtil.stopPlay();\n                        playingMessage.setPlaying(false);\n                    }\n                    audioUtil.startPlay(FileCache.getInstance().getCachedFilePath(audio.url));\n                    playingMessage = message;\n                    message.setPlaying(true);\n                    if (!message.isListened() && !message.isOutgoing) {\n                        message.setListened(true);\n                        markMessageListened(message);\n                    }\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n\n}"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/MessageBaseActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia;\n\nimport android.database.Cursor;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.provider.OpenableColumns;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.util.Pair;\nimport android.widget.Toast;\n\nimport com.amap.api.services.core.AMapException;\nimport com.amap.api.services.core.LatLonPoint;\nimport com.amap.api.services.geocoder.GeocodeResult;\nimport com.amap.api.services.geocoder.GeocodeSearch;\nimport com.amap.api.services.geocoder.RegeocodeQuery;\nimport com.amap.api.services.geocoder.RegeocodeResult;\nimport com.beetle.bauhinia.activity.BaseActivity;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.IMessageDB;\nimport com.beetle.bauhinia.db.MessageIterator;\nimport com.beetle.bauhinia.db.message.ACK;\nimport com.beetle.bauhinia.db.message.Audio;\nimport com.beetle.bauhinia.db.message.Image;\nimport com.beetle.bauhinia.db.message.Location;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Revoke;\nimport com.beetle.bauhinia.db.message.Text;\nimport com.beetle.bauhinia.db.message.TimeBase;\nimport com.beetle.bauhinia.db.message.Video;\nimport com.beetle.bauhinia.outbox.OutboxObserver;\nimport com.beetle.bauhinia.tools.AudioUtil;\nimport com.beetle.bauhinia.tools.FileCache;\nimport com.beetle.bauhinia.tools.FileDownloader;\nimport com.beetle.bauhinia.tools.TimeUtil;\nimport com.beetle.bauhinia.tools.VideoUtil;\nimport com.beetle.im.IMService;\nimport com.beetle.im.MessageACK;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.UUID;\nimport com.beetle.imkit.R;\n\n/*\n at对象的用户名问题\n 本地显示的优先级 用户备注 > 群内昵称 > 用户名\n\n 派生类实现\n protected void checkAtName(IMessage messages)\n 替换掉消息内容中at对象的用户名\n\n 派生类重载\n protected void sendTextMessage(String text, List<Long> at, List<String> atNames)\n 将有可能是本地备注的用户名修改为群内昵称或用户名\n\n */\nabstract public class MessageBaseActivity extends BaseActivity implements\n        FileDownloader.FileDownloaderObserver,\n        OutboxObserver {\n    protected static final String TAG = \"imservice\";\n\n    //消息撤回的时限\n    public static final int REVOKE_EXPIRE = 120;\n\n    public static final int PAGE_SIZE = 10;\n    public static final int FILE_SIZE_LIMIT = 16*1024*1024;\n\n    //app 启动时间戳，app启动时初始化\n    public static int uptime;\n    static {\n        uptime = now();\n        Log.i(TAG, \"uptime:\" + uptime);\n    }\n\n    public static int now() {\n        Date date = new Date();\n        long t = date.getTime();\n        return (int)(t/1000);\n    }\n\n\n    protected int pageSize = PAGE_SIZE;\n    protected String conversationID;//uid or groupid or storeid\n\n    protected long messageID;\n    protected boolean hasLateMore;\n    protected boolean hasEarlierMore;\n    protected IMessageDB messageDB;\n\n    protected ArrayList<IMessage> messages = new ArrayList<IMessage>();\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n    protected void loadData() {\n        messages = new ArrayList<IMessage>();\n        List<IMessage> newMessages;\n        if (messageID > 0) {\n            newMessages = this.loadConversationData(messageID);\n        } else {\n            newMessages = this.loadConversationData();\n        }\n\n        //删除重复的消息,过滤掉不显示的消息\n        HashSet<String> uuidSet = new HashSet<String>();\n        for (int i = 0; i < newMessages.size(); i++) {\n            IMessage msg = newMessages.get(i);\n            if (!TextUtils.isEmpty(msg.getUUID()) && uuidSet.contains(msg.getUUID())) {\n                continue;\n            }\n            if (msg.getType() == MessageContent.MessageType.MESSAGE_P2P_SESSION) {\n                continue;\n            }\n            messages.add(msg);\n            if (!TextUtils.isEmpty(msg.getUUID())) {\n                uuidSet.add(msg.getUUID());\n            }\n        }\n        int count = messages.size();\n\n        prepareMessage(messages, count);\n        resetMessageTimeBase();\n    }\n\n\n    protected int loadEarlierData() {\n        int newCount = 0;\n        if (!hasEarlierMore) {\n            return newCount;\n        }\n        if (messages.size() == 0) {\n            return newCount;\n        }\n\n        IMessage firstMsg = null;\n        for (int i  = 0; i < messages.size(); i++) {\n            IMessage msg = messages.get(i);\n            if (msg.msgLocalID > 0) {\n                firstMsg = msg;\n                break;\n            }\n        }\n        if (firstMsg == null) {\n            return newCount;\n        }\n\n        HashSet<String> uuidSet = new HashSet<String>();\n        for (int i  = 0; i < messages.size(); i++) {\n            IMessage msg = messages.get(i);\n            if (!TextUtils.isEmpty(msg.getUUID())) {\n                uuidSet.add(msg.getUUID());\n            }\n        }\n\n        List<IMessage> newMessages = this.loadEarlierData(firstMsg.msgLocalID);\n        int count = newMessages.size();\n\n        if (count == 0) {\n            hasEarlierMore = false;\n            return newCount;\n        }\n        for (int i = count-1; i >= 0; i--) {\n            IMessage msg = newMessages.get(i);\n            //重复的消息\n            if (!TextUtils.isEmpty(msg.getUUID()) && uuidSet.contains(msg.getUUID())) {\n                continue;\n            }\n            //不显示的消息\n            if (msg.getType() == MessageContent.MessageType.MESSAGE_P2P_SESSION) {\n                continue;\n            }\n\n            messages.add(0, msg);\n            newCount++;\n            if (!TextUtils.isEmpty(msg.getUUID())) {\n                uuidSet.add(msg.getUUID());\n            }\n        }\n\n        prepareMessage(messages, count);\n        resetMessageTimeBase();\n        return newCount;\n    }\n\n    protected int loadLaterData() {\n        int newCount = 0;\n        if (!this.hasLateMore || messageID == 0) {\n            return newCount;\n        }\n\n        if (messages.size() == 0) {\n            return newCount;\n        }\n\n        HashSet<String> uuidSet = new HashSet<String>();\n        for (int i  = 0; i < messages.size(); i++) {\n            IMessage msg = messages.get(i);\n            if (!TextUtils.isEmpty(msg.getUUID())) {\n                uuidSet.add(msg.getUUID());\n            }\n        }\n\n        IMessage msg = messages.get(messages.size() - 1);\n        List<IMessage> newMessages = this.loadLaterData(msg.msgLocalID);\n        int count = newMessages.size();\n\n        if (count == 0) {\n            this.hasLateMore = false;\n            return newCount;\n        }\n\n        for (int i = 0; i < count; i++) {\n            IMessage m = newMessages.get(i);\n            //重复消息\n            if (!TextUtils.isEmpty(m.getUUID()) && uuidSet.contains(m.getUUID())) {\n                continue;\n            }\n            //不需要显示的消息\n            if (msg.getType() == MessageContent.MessageType.MESSAGE_P2P_SESSION) {\n                continue;\n            }\n\n            prepareMessage(m);\n            messages.add(m);\n            newCount++;\n            if (!TextUtils.isEmpty(msg.getUUID())) {\n                uuidSet.add(msg.getUUID());\n            }\n        }\n        resetMessageTimeBase();\n        return newCount;\n    }\n\n\n    private List<IMessage> loadConversationData() {\n        ArrayList<IMessage> messages = new ArrayList<IMessage>();\n        int count = 0;\n        MessageIterator iter = createMessageIterator();\n        while (iter != null) {\n            IMessage msg = iter.next();\n            if (msg == null) {\n                break;\n            }\n\n            messages.add(0, msg);\n            if (++count >= pageSize) {\n                break;\n            }\n        }\n        return messages;\n    }\n\n    private List<IMessage> loadConversationData(long messageID) {\n        HashSet<String> uuidSet = new HashSet<String>();\n        ArrayList<IMessage> messages = new ArrayList<IMessage>();\n\n        int count = 0;\n        MessageIterator iter;\n\n        iter = createMiddleMessageIterator(messageID);\n\n        while (iter != null) {\n            IMessage msg = iter.next();\n            if (msg == null) {\n                break;\n            }\n\n            //不加载重复的消息\n            if (!TextUtils.isEmpty(msg.getUUID()) && uuidSet.contains(msg.getUUID())) {\n                continue;\n            }\n\n            if (!TextUtils.isEmpty(msg.getUUID())) {\n                uuidSet.add(msg.getUUID());\n            }\n            messages.add(0, msg);\n            if (++count >= pageSize*2) {\n                break;\n            }\n        }\n\n        return messages;\n    }\n\n    private List<IMessage> loadEarlierData(long messageID) {\n        ArrayList<IMessage> messages = new ArrayList<IMessage>();\n        int count = 0;\n        MessageIterator iter = createForwardMessageIterator(messageID);\n        while (iter != null) {\n            IMessage msg = iter.next();\n            if (msg == null) {\n                break;\n            }\n            messages.add(0, msg);\n            if (++count >= pageSize) {\n                break;\n            }\n        }\n        return messages;\n    }\n\n    private List<IMessage> loadLaterData(long messageID) {\n        ArrayList<IMessage> messages = new ArrayList<IMessage>();\n        int count = 0;\n        MessageIterator iter = createBackwardMessageIterator(messageID);\n        while (true) {\n            IMessage msg = iter.next();\n            if (msg == null) {\n                break;\n            }\n            messages.add(msg);\n            if (++count >= pageSize) {\n                break;\n            }\n        }\n        return messages;\n    }\n\n\n\n    //加载消息发送者的名称和头像信息\n    protected void loadUserName(IMessage msg) {\n        if (msg.sender == 0) {\n            return;\n        }\n\n        MessageActivity.User u = getUser(msg.sender);\n\n        msg.setSenderAvatar(u.avatarURL);\n        if (TextUtils.isEmpty(u.name)) {\n            msg.setSenderName(u.identifier);\n            final IMessage fmsg = msg;\n            asyncGetUser(msg.sender, new MessageActivity.GetUserCallback() {\n                @Override\n                public void onUser(MessageActivity.User u) {\n                    fmsg.setSenderName(u.name);\n                    fmsg.setSenderAvatar(u.avatarURL);\n                }\n            });\n        } else {\n            msg.setSenderName(u.name);\n        }\n    }\n\n\n    void checkMessageFailureFlag(IMessage msg) {\n        if (msg.isOutgoing) {\n            if (msg.timestamp < uptime && !msg.isAck()) {\n                msg.setFailure(true);\n                markMessageFailure(msg);\n            }\n        }\n    }\n\n    protected void updateNotificationDesc(IMessage imsg) {\n        if (imsg.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n            Revoke revoke = (Revoke)imsg.content;\n            if (imsg.isOutgoing) {\n                revoke.description = getString(R.string.message_revoked, getString(R.string.you));\n            } else {\n                MessageActivity.User u = this.getUser(imsg.sender);\n                String name = !TextUtils.isEmpty(u.name) ? u.name : u.identifier;\n                revoke.description = getString(R.string.message_revoked, name);\n            }\n        } else if (imsg.getType() == MessageContent.MessageType.MESSAGE_ACK) {\n            ACK ack = (ACK)imsg.content;\n            if (ack.error == MessageACK.MESSAGE_ACK_NOT_YOUR_FRIEND) {\n                ack.description = getString(R.string.message_not_friend);\n            } else if (ack.error == MessageACK.MESSAGE_ACK_IN_YOUR_BLACKLIST) {\n                ack.description = getString(R.string.message_refuesed);\n            } else if (ack.error == MessageACK.MESSAGE_ACK_NOT_MY_FRIEND) {\n                ack.description = getString(R.string.message_not_my_friend);\n            }\n        }\n    }\n\n\n    protected void queryLocation(final IMessage msg) {\n        final Location loc = (Location)msg.content;\n\n        msg.setGeocoding(true);\n        // 第一个参数表示一个Latlng，第二参数表示范围多少米，第三个参数表示是火系坐标系还是GPS原生坐标系\n        RegeocodeQuery query = new RegeocodeQuery(new LatLonPoint(loc.latitude, loc.longitude), 200, GeocodeSearch.AMAP);\n\n        GeocodeSearch mGeocodeSearch = new GeocodeSearch(this);\n        mGeocodeSearch.setOnGeocodeSearchListener(new GeocodeSearch.OnGeocodeSearchListener() {\n            @Override\n            public void onRegeocodeSearched(RegeocodeResult result, int rCode) {\n                if (rCode == AMapException.CODE_AMAP_SUCCESS) {\n                    if (result != null && result.getRegeocodeAddress() != null\n                            && result.getRegeocodeAddress().getFormatAddress() != null) {\n                        String address = result.getRegeocodeAddress().getFormatAddress();\n                        Log.i(TAG, \"address:\" + address);\n                        loc.address = address;\n                        saveMessageAttachment(msg, address);\n                    }\n                } else {\n                    // 定位失败;\n                }\n                msg.setGeocoding(false);\n            }\n\n            @Override\n            public void onGeocodeSearched(GeocodeResult geocodeResult, int i) {\n\n            }\n        });\n\n        mGeocodeSearch.getFromLocationAsyn(query);// 设置同步逆地理编码请求\n    }\n\n    protected void downloadMessageContent(IMessage msg) {\n        if (msg.content.getType() == MessageContent.MessageType.MESSAGE_AUDIO) {\n            Audio audio = (Audio) msg.content;\n            FileDownloader downloader = FileDownloader.getInstance();\n            if (!FileCache.getInstance().isCached(audio.url) && !downloader.isDownloading(msg)) {\n                downloader.download(msg);\n            }\n            msg.setDownloading(downloader.isDownloading(msg));\n        } else if (msg.content.getType() == MessageContent.MessageType.MESSAGE_IMAGE) {\n            Image image = (Image)msg.content;\n            FileDownloader downloader = FileDownloader.getInstance();\n            //加密的图片消息需要手动下载后解密\n            if (msg.secret && !image.url.startsWith(\"file:\") &&\n                    !FileCache.getInstance().isCached(image.url) &&\n                    !downloader.isDownloading(msg)) {\n                downloader.download(msg);\n            }\n            msg.setDownloading(downloader.isDownloading(msg));\n        } else if (msg.content.getType() == MessageContent.MessageType.MESSAGE_LOCATION) {\n            Location loc = (Location)msg.content;\n            if (TextUtils.isEmpty(loc.address)) {\n                queryLocation(msg);\n            }\n        } else if (msg.content.getType() == MessageContent.MessageType.MESSAGE_VIDEO) {\n            Video video = (Video)msg.content;\n            FileDownloader downloader = FileDownloader.getInstance();\n            //加密的图片消息需要手动下载后解密\n            if (msg.secret && !video.thumbnail.startsWith(\"file:\") &&\n                    !FileCache.getInstance().isCached(video.thumbnail) &&\n                    !downloader.isDownloading(msg)) {\n                downloader.download(msg);\n            }\n            msg.setDownloading(downloader.isDownloading(msg));\n        }\n    }\n\n    protected void checkAtName(IMessage message) {\n\n    }\n\n    protected void sendReaded(IMessage message) {\n\n    }\n\n    protected void prepareMessage(ArrayList<IMessage> messages, int count) {\n        for (int i = 0; i < count; i++) {\n            IMessage msg = messages.get(i);\n            prepareMessage(msg);\n        }\n    }\n\n    protected void prepareMessage(IMessage message) {\n        message.isOutgoing = getMessageOutgoing(message);\n\n        loadUserName(message);\n        downloadMessageContent(message);\n        updateNotificationDesc(message);\n        checkMessageFailureFlag(message);\n        checkAtName(message);\n        sendReaded(message);\n    }\n\n    protected void saveMessageAttachment(IMessage msg, String address) {\n        this.messageDB.saveMessageAttachment(msg, address);\n    }\n\n    protected void saveMessage(IMessage imsg) {\n        this.messageDB.saveMessage(imsg);\n    }\n\n    protected void removeMessage(IMessage imsg) {\n        this.messageDB.removeMessage(imsg);\n    }\n\n    protected void markMessageListened(IMessage imsg) {\n        this.messageDB.markMessageListened(imsg);\n    }\n\n    protected void markMessageFailure(IMessage imsg) {\n        this.messageDB.markMessageFailure(imsg);\n    }\n\n\n    protected void eraseMessageFailure(IMessage imsg) {\n        this.messageDB.eraseMessageFailure(imsg);\n    }\n\n\n    protected void resetMessageTimeBase() {\n        ArrayList<IMessage> newMessages = new ArrayList<IMessage>();\n        IMessage lastMsg = null;\n        for (int i = 0; i < messages.size(); i++) {\n            IMessage msg = messages.get(i);\n            if (msg.content.getType() == MessageContent.MessageType.MESSAGE_TIME_BASE) {\n                continue;\n            }\n            //间隔10分钟，添加时间分割线\n            if (lastMsg == null || msg.timestamp - lastMsg.timestamp > 10*60) {\n                TimeBase timeBase = TimeBase.newTimeBase(msg.timestamp);\n                String s = TimeUtil.formatTimeBase(timeBase.timestamp);\n                timeBase.description = s;\n                IMessage t = new IMessage();\n                t.content = timeBase;\n                t.timestamp = msg.timestamp;\n                newMessages.add(t);\n            }\n            newMessages.add(msg);\n\n            lastMsg = msg;\n        }\n        messages = newMessages;\n    }\n\n\n    protected void deleteMessage(IMessage imsg) {\n        int index = -1;\n        for (int i = 0; i < messages.size(); i++) {\n            IMessage m = messages.get(i);\n            if (m.msgLocalID == imsg.msgLocalID) {\n                index = i;\n                break;\n            }\n        }\n        if (index != -1) {\n            messages.remove(index);\n        }\n    }\n\n    protected void replaceMessage(IMessage imsg, IMessage other) {\n        int index = -1;\n        for (int i = 0; i < messages.size(); i++) {\n            IMessage m = messages.get(i);\n            if (m.msgLocalID == imsg.msgLocalID) {\n                index = i;\n                break;\n            }\n        }\n        if (index != -1) {\n            messages.set(index, other);\n        }\n    }\n\n    protected void insertMessage(IMessage imsg) {\n        IMessage lastMsg = null;\n        if (messages.size() > 0) {\n            lastMsg = messages.get(messages.size() - 1);\n        }\n        //间隔10分钟，添加时间分割线\n        if (lastMsg == null || imsg.timestamp - lastMsg.timestamp > 10*60) {\n            TimeBase timeBase = TimeBase.newTimeBase(imsg.timestamp);\n            String s = TimeUtil.formatTimeBase(timeBase.timestamp);\n            timeBase.description = s;\n            IMessage t = new IMessage();\n            t.content = timeBase;\n            t.timestamp = imsg.timestamp;\n            messages.add(t);\n        }\n\n        checkAtName(imsg);\n        messages.add(imsg);\n    }\n\n    protected void sendTextMessage(String text, List<Long> at, List<String> atNames) {\n        if (text.length() == 0) {\n            return;\n        }\n\n        sendMessageContent(Text.newText(text, at, atNames));\n    }\n\n\n    protected void sendVideoMessage(String path, String thumbPath) {\n        File f = new File(path);\n        File thumbFile = new File(thumbPath);\n        if (!f.exists() || !thumbFile.exists()) {\n            return;\n        }\n\n        final VideoUtil.Metadata meta = VideoUtil.getVideoMetadata(path);\n        Log.i(TAG, \"video mime:\" + meta.videoMime + \" audio mime:\" + meta.audioMime);\n\n        if (meta.duration < 1000) {\n            Toast.makeText(this, getString(R.string.video_record_duration_warning), Toast.LENGTH_SHORT).show();\n            return;\n        }\n\n        if (!TextUtils.isEmpty(meta.videoMime) && !VideoUtil.isH264(meta.videoMime)) {\n            Toast.makeText(this, getString(R.string.unsupported_video_encoding_warning), Toast.LENGTH_SHORT).show();\n            return;\n        }\n\n        if (!TextUtils.isEmpty(meta.audioMime) && !VideoUtil.isAcc(meta.audioMime)) {\n            Toast.makeText(this, getString(R.string.unsupported_video_encoding_warning), Toast.LENGTH_SHORT).show();\n            return;\n        }\n\n        final int duration = meta.duration/1000;//单位秒\n        Log.i(TAG, \"video path:\" + path + \" file size:\" + f.length() + \"video size:\" + meta.width + \" \" + meta.height + \" duration:\" + meta.duration);\n\n        try {\n            String thumbURL = localImageURL();\n            FileCache.getInstance().moveFile(thumbURL, thumbPath);\n            String p1 = FileCache.getInstance().getCachedFilePath(thumbURL);\n\n            final String videoURL = localVideoURL();\n            FileCache.getInstance().moveFile(videoURL, path);\n\n            sendMessageContent(Video.newVideo(videoURL, \"file:\" + p1, meta.width, meta.height, duration));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    protected void sendImageMessage(Bitmap bmp) {\n        //https://www.jianshu.com/p/5b77da571a5c\n        double w = bmp.getWidth();\n        double h = bmp.getHeight();\n        double rate = w > h ? w/h : h/w;\n        int scalePolicy = -1;// 0 origin, 1 max 1280, 2  min 800\n        if (w <= 1280 && h <= 1280) {\n            scalePolicy = 0;\n        } else if (w > 1280) {\n            if (rate <= 2) {\n                //max 1280\n                scalePolicy = 1;\n            } else {\n                if (h <= 1280){\n                    scalePolicy = 0;\n                } else if (h > 1280) {\n                    //min 800\n                    scalePolicy = 2;\n                }\n            }\n        } else if (h > 1280) {\n            if (rate <= 2) {\n                //max 1280\n                scalePolicy = 1;\n            } else {\n                //w <= 1280\n                scalePolicy = 0;\n            }\n        }\n\n        double newHeight = 0;\n        double newWidth = 0;\n        Bitmap bigBMP;\n        if (scalePolicy == 0) {\n            bigBMP = bmp;\n            newWidth = bmp.getWidth();\n            newHeight = bmp.getHeight();\n        } else if (scalePolicy == 1) {\n            if (w > h) {\n                newWidth = 1280;\n                newHeight = 1280/rate;\n            } else {\n                newHeight = 1280;\n                newWidth = 1280/rate;\n            }\n            bigBMP = Bitmap.createScaledBitmap(bmp, (int)newWidth, (int)newHeight, true);\n        } else if (scalePolicy == 2) {\n            if (w > h) {\n                newWidth = 800*rate;\n                newHeight = 800;\n            } else {\n                newHeight = 800*rate;\n                newWidth = 800;\n            }\n            bigBMP = Bitmap.createScaledBitmap(bmp, (int)newWidth, (int)newHeight, true);\n        } else {\n            Log.w(TAG, \"invalid scale policy, width:\" + w + \" height:\" + h);\n            bigBMP = bmp;\n            newWidth = bmp.getWidth();\n            newHeight = bmp.getHeight();\n        }\n\n        double sw = 256.0;\n        double sh = 256.0*h/w;\n\n        Bitmap thumbnail = Bitmap.createScaledBitmap(bmp, (int)sw, (int)sh, true);\n        ByteArrayOutputStream os = new ByteArrayOutputStream();\n        bigBMP.compress(Bitmap.CompressFormat.JPEG, 50, os);\n        ByteArrayOutputStream os2 = new ByteArrayOutputStream();\n        thumbnail.compress(Bitmap.CompressFormat.JPEG, 50, os2);\n\n        String originURL = localImageURL();\n        String thumbURL = localImageURL();\n        try {\n            FileCache.getInstance().storeByteArray(originURL, os);\n            FileCache.getInstance().storeByteArray(thumbURL, os2);\n\n            String path = FileCache.getInstance().getCachedFilePath(originURL);\n            String thumbPath = FileCache.getInstance().getCachedFilePath(thumbURL);\n\n            String tpath = path + \"@256w_256h_0c\";\n            File f = new File(thumbPath);\n            File t = new File(tpath);\n            f.renameTo(t);\n\n            sendMessageContent(Image.newImage(\"file:\" + path, (int)newWidth, (int)newHeight));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    protected void sendAudioMessage(String tfile) {\n        try {\n            long mduration = AudioUtil.getAudioDuration(tfile);\n\n            if (mduration < 1000) {\n                Toast.makeText(this, getString(R.string.voice_record_duration_warning), Toast.LENGTH_SHORT).show();\n                return;\n            }\n            long duration = mduration/1000;\n\n            String url = localAudioURL();\n\n            Audio audio = Audio.newAudio(url, duration);\n            FileInputStream is = new FileInputStream(new File(tfile));\n            Log.i(TAG, \"store audio url:\" + audio.url);\n            FileCache.getInstance().storeFile(audio.url, is);\n\n            sendMessageContent(audio);\n        } catch (IllegalStateException e) {\n            e.printStackTrace();\n            return;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return;\n        }\n    }\n\n    protected void sendLocationMessage(float longitude, float latitude, String address) {\n        Location loc = Location.newLocation(latitude, longitude);\n        loc.address = address;\n        sendMessageContent(loc);\n    }\n\n    protected void sendFileMessage(Uri uri) {\n        try {\n            InputStream in = getContentResolver().openInputStream(uri);\n            Pair<String, Long> fileInfo = getFileInfo(uri);\n\n            String filename = fileInfo.first;\n            long fileSize = fileInfo.second;\n            if (TextUtils.isEmpty(filename)) {\n                Log.i(TAG, \"can't get filename\");\n                return;\n            }\n            if (fileSize > FILE_SIZE_LIMIT) {\n                String warning = String.format(getString(R.string.file_size_limit_warning), FILE_SIZE_LIMIT/(1024*1024));\n                Toast.makeText(this, warning, Toast.LENGTH_SHORT).show();\n                return;\n            }\n            String ext = \"\";\n            int index = filename.lastIndexOf(\".\");\n            if (index != -1) {\n                ext = filename.substring(index);\n            }\n\n            final String fileURL = localFileURL(ext);\n\n            FileCache.getInstance().storeFile(fileURL, in);\n\n            File f = new File(FileCache.getInstance().getCachedFilePath(fileURL));\n\n            int size = (int)(f.length());\n\n            Log.i(TAG, \"file size:\" + size + \" filename:\" + filename);\n\n            sendMessageContent(com.beetle.bauhinia.db.message.File.newFile(fileURL, filename, size));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private Pair<String, Long> getFileInfo(Uri uri) {\n        Cursor cursor = getContentResolver().query(uri, null, null, null, null);\n\n        if (cursor.getCount() <= 0) {\n            cursor.close();\n            return null;\n        }\n\n\n        cursor.moveToFirst();\n\n        String fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));\n\n        long size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));\n\n        cursor.close();\n\n        Pair<String, Long> pair = Pair.create(fileName, size);\n        return pair;\n    }\n\n    protected void sendMessageContent(MessageContent content) {\n        IMessage imsg = this.newOutMessage(content);\n\n        imsg.setContent(content);\n        imsg.timestamp = now();\n        imsg.isOutgoing = true;\n        saveMessage(imsg);\n        loadUserName(imsg);\n\n        if (imsg.content.getType() == MessageContent.MessageType.MESSAGE_LOCATION) {\n            Location loc = (Location)imsg.content;\n\n            if (TextUtils.isEmpty(loc.address)) {\n                queryLocation(imsg);\n            }\n        }\n\n        sendMessage(imsg);\n        insertMessage(imsg);\n    }\n\n\n    @Override\n    public void onAudioUploadSuccess(IMessage imsg, String url) {\n        Log.i(TAG, \"audio upload success:\" + url);\n        IMessage m = findMessage(imsg.content.getUUID());\n        if (m != null) {\n            Audio audio = (Audio)m.content;\n            Audio newAudio = Audio.newAudio(url, audio.duration);\n            newAudio.generateRaw(audio.getUUID(), audio.getReference(), audio.getGroupId());\n            m.content = newAudio;\n            m.setUploading(false);\n        }\n    }\n\n    @Override\n    public void onAudioUploadFail(IMessage msg) {\n        Log.i(TAG, \"audio upload fail\");\n        IMessage m = findMessage(msg.content.getUUID());\n        if (m != null) {\n            m.setFailure(true);\n            m.setUploading(false);\n        }\n    }\n\n    @Override\n    public void onImageUploadSuccess(IMessage msg, String url) {\n        Log.i(TAG, \"image upload success:\" + url);\n        IMessage m = findMessage(msg.content.getUUID());\n        if (m != null) {\n            Image image = (Image)m.content;\n            Image newImage = Image.newImage(url, image.width, image.height);\n            newImage.generateRaw(image.getUUID(), image.getReference(), image.getGroupId());\n            m.content = newImage;\n            m.setUploading(false);\n        }\n    }\n\n    @Override\n    public void onImageUploadFail(IMessage msg) {\n        Log.i(TAG, \"image upload fail\");\n        IMessage m = findMessage(msg.content.getUUID());\n        if (m != null) {\n            m.setFailure(true);\n            m.setUploading(false);\n        }\n    }\n\n    @Override\n    public void onVideoUploadSuccess(IMessage msg, String url, String thumbURL) {\n        Log.i(TAG, \"video upload success:\" + url + \" thumb url:\" + thumbURL);\n        IMessage m = findMessage(msg.content.getUUID());\n        if (m != null) {\n            Video video = (Video)m.content;\n            Video newVideo = Video.newVideo(url, thumbURL, video.width, video.height, video.duration);\n            newVideo.generateRaw(video.getUUID(), video.getReference(), video.getGroupId());\n            m.content = newVideo;\n            m.setUploading(false);\n        }\n    }\n\n    @Override\n    public void onVideoUploadFail(IMessage msg) {\n        Log.i(TAG, \"video upload fail\");\n        IMessage m = findMessage(msg.content.getUUID());\n        if (m != null) {\n            m.setFailure(true);\n            m.setUploading(false);\n        }\n    }\n\n    @Override\n    public void onFileUploadSuccess(IMessage msg, String url) {\n        Log.i(TAG, \"file upload success:\" + url);\n        IMessage m = findMessage(msg.content.getUUID());\n        if (m != null) {\n            com.beetle.bauhinia.db.message.File file = (com.beetle.bauhinia.db.message.File)m.content;\n            com.beetle.bauhinia.db.message.File newFile = com.beetle.bauhinia.db.message.File.newFile(url, file.filename, file.size);\n            newFile.generateRaw(file.getUUID(), file.getReference(), file.getGroupId());\n            m.content = newFile;\n            m.setUploading(false);\n        }\n    }\n\n    @Override\n    public void onFileUploadFail(IMessage msg) {\n        Log.i(TAG, \"file upload fail\");\n        IMessage m = findMessage(msg.content.getUUID());\n        if (m != null) {\n            m.setFailure(true);\n            m.setUploading(false);\n        }\n    }\n\n    @Override\n    public void onFileDownloadSuccess(IMessage msg) {\n        Log.i(TAG, \"audio download success\");\n        IMessage m = findMessage(msg.content.getUUID());\n        if (m != null) {\n            m.setDownloading(false);\n        }\n    }\n\n    @Override\n    public void onFileDownloadFail(IMessage msg) {\n        Log.i(TAG, \"audio download fail\");\n        IMessage m = findMessage(msg.content.getUUID());\n        if (m != null) {\n            m.setDownloading(false);\n        }\n    }\n\n    protected void revoke(IMessage msg) {\n        if (TextUtils.isEmpty(msg.getUUID())) {\n            return;\n        }\n\n        int now = now();\n        if (now - msg.timestamp > REVOKE_EXPIRE) {\n            Toast.makeText(this, getString(R.string.revoke_timed_out), Toast.LENGTH_SHORT).show();\n            return;\n        }\n\n        if (IMService.getInstance().getConnectState() != IMService.ConnectState.STATE_CONNECTED) {\n            Toast.makeText(this, getString(R.string.revoke_connection_disconnect), Toast.LENGTH_SHORT).show();\n            return;\n        }\n\n        Revoke revoke = Revoke.newRevoke(msg.getUUID());\n        IMessage imsg = this.newOutMessage(revoke);\n        imsg.setContent(revoke);\n        imsg.timestamp = now();\n        imsg.isOutgoing = true;\n        sendMessage(imsg);\n    }\n\n    protected void resend(IMessage msg) {\n        eraseMessageFailure(msg);\n        msg.setFailure(false);\n        this.sendMessage(msg);\n    }\n\n    abstract protected boolean getMessageOutgoing(IMessage msg);\n    abstract protected IMessage newOutMessage(MessageContent content);\n    abstract protected void sendMessage(IMessage imsg);\n    abstract protected MessageIterator createMessageIterator();\n    abstract protected MessageIterator createForwardMessageIterator(long messageID);\n    abstract protected MessageIterator createBackwardMessageIterator(long messageID);\n    abstract protected MessageIterator createMiddleMessageIterator(long messageID);\n\n\n    protected IMessage findMessage(long msgLocalID) {\n        for (IMessage imsg : messages) {\n            if (imsg.msgLocalID == msgLocalID) {\n                return imsg;\n            }\n        }\n        return null;\n    }\n\n    protected IMessage findMessage(String uuid) {\n        if (TextUtils.isEmpty(uuid)) {\n            return null;\n        }\n        for (IMessage imsg : messages) {\n            if (imsg.getUUID().equals(uuid)) {\n                return imsg;\n            }\n        }\n        return null;\n    }\n\n\n\n    protected ArrayList<IMessage> getImageMessages() {\n        ArrayList<IMessage> images = new ArrayList<IMessage>();\n\n        MessageIterator iter = createMessageIterator();\n        while (iter != null) {\n            IMessage msg = iter.next();\n            if (msg == null) {\n                break;\n            }\n\n            if (msg.content.getType() == MessageContent.MessageType.MESSAGE_IMAGE) {\n                Image image = ((Image)msg.content);\n                if (msg.secret && !image.url.startsWith(\"file:\")) {\n                    String path = FileCache.getInstance().getCachedFilePath(image.url);\n                    if (path == null) {\n                        //图片未下载完成或者解密失败\n                        continue;\n                    }\n                    String url = \"file:\" + path;\n                    Image newImage = Image.newImage(url, image.width, image.height);\n                    newImage.generateRaw(image.getUUID(), image.getReference(), image.getGroupId());\n                    msg.content = newImage;\n\n                    images.add(msg);\n                } else {\n                    images.add(msg);\n                }\n            }\n        }\n        Collections.reverse(images);\n        return images;\n    }\n\n    protected String localFileURL(String ext) {\n        UUID uuid = UUID.randomUUID();\n        return \"http://localhost/videos/\"+ uuid.toString() + ext;\n    }\n\n    protected String localVideoURL() {\n        UUID uuid = UUID.randomUUID();\n        return \"http://localhost/videos/\"+ uuid.toString() + \".mp4\";\n    }\n\n    protected String localImageURL() {\n        UUID uuid = UUID.randomUUID();\n        return \"http://localhost/images/\"+ uuid.toString() + \".png\";\n    }\n\n    protected String localAudioURL() {\n        UUID uuid = UUID.randomUUID();\n        return \"http://localhost/audios/\" + uuid.toString() + \".amr\";\n    }\n\n\n    public static class User {\n        public long uid;\n        public String name;\n        public String avatarURL;\n\n        //name为nil时，界面显示identifier字段\n        public String identifier;\n    }\n\n    protected User getUser(long uid) {\n        User u = new User();\n        u.uid = uid;\n        u.name = null;\n        u.avatarURL = \"\";\n        u.identifier = String.format(\"%d\", uid);\n        return u;\n    }\n\n    public interface GetUserCallback {\n        void onUser(User u);\n    }\n\n    protected void asyncGetUser(long uid, GetUserCallback cb) {\n\n    }\n\n\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/PeerMessageActivity.java",
    "content": "package com.beetle.bauhinia;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.widget.Toast;\n\nimport com.beetle.bauhinia.db.*;\nimport com.beetle.bauhinia.db.message.ACK;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Revoke;\nimport com.beetle.bauhinia.tools.FileDownloader;\nimport com.beetle.bauhinia.outbox.PeerOutbox;\n\nimport com.beetle.im.IMMessage;\nimport com.beetle.im.IMService;\nimport com.beetle.im.IMServiceObserver;\nimport com.beetle.im.MessageACK;\nimport com.beetle.im.PeerMessageObserver;\n\n\npublic class PeerMessageActivity extends MessageActivity implements\n        IMServiceObserver, PeerMessageObserver {\n    protected long currentUID;\n    protected long peerUID;\n    protected String peerName;\n    protected String peerAvatar;\n    protected boolean secret;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        items[ITEM_VIDEO_CALL_ID] = false;\n        super.onCreate(savedInstanceState);\n        isShowUserName = false;\n        isShowReaded = false;\n        isShowReply = false;\n\n\n        Intent intent = getIntent();\n\n        currentUID = intent.getLongExtra(\"current_uid\", 0);\n        if (currentUID == 0) {\n            Log.e(TAG, \"current uid is 0\");\n            return;\n        }\n        peerUID = intent.getLongExtra(\"peer_uid\", 0);\n        if (peerUID == 0) {\n            Log.e(TAG, \"peer uid is 0\");\n            return;\n        }\n        peerName = intent.getStringExtra(\"peer_name\");\n        if (peerName == null) {\n            peerName = \"\";\n        }\n        peerAvatar = intent.getStringExtra(\"peer_avatar\");\n        if (peerAvatar == null) {\n            peerAvatar = \"\";\n        }\n\n        secret = intent.getBooleanExtra(\"secret\", false);\n        messageID = intent.getIntExtra(\"message_id\", 0);\n\n        Log.i(TAG, \"local id:\" + currentUID +  \"peer id:\" + peerUID);\n\n        if (secret) {\n            getSupportActionBar().setTitle(peerName + \"(密)\");\n        } else {\n            getSupportActionBar().setTitle(peerName);\n        }\n\n        if (secret) {\n            messageDB = EPeerMessageDB.getInstance();\n        } else {\n            messageDB = PeerMessageDB.getInstance();\n        }\n\n        this.hasLateMore = this.messageID > 0;\n        this.hasEarlierMore = true;\n        this.loadData();\n        if (this.messages.size() > 0) {\n            if (messageID > 0) {\n                int index = -1;\n                for (int i = 0; i < this.messages.size(); i++) {\n                    if (messageID == this.messages.get(i).msgLocalID) {\n                        index = i;\n                        break;\n                    }\n                }\n\n                if (index != -1) {\n                    listview.setSelection(index);\n                }\n            } else {\n                //显示最后一条消息\n                listview.setSelection(this.messages.size() - 1);\n            }\n        }\n\n        PeerOutbox.getInstance().addObserver(this);\n        IMService.getInstance().addObserver(this);\n        IMService.getInstance().addPeerObserver(this);\n        FileDownloader.getInstance().addObserver(this);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        Log.i(TAG, \"peer message activity destory\");\n\n        PeerOutbox.getInstance().removeObserver(this);\n        IMService.getInstance().removeObserver(this);\n        IMService.getInstance().removePeerObserver(this);\n        FileDownloader.getInstance().removeObserver(this);\n    }\n\n\n    @Override\n    public void onConnectState(IMService.ConnectState state) {\n        if (state == IMService.ConnectState.STATE_CONNECTED) {\n            enableSend();\n        } else {\n            disableSend();\n        }\n    }\n\n\n    @Override\n    public void onPeerMessage(IMMessage msg) {\n        if (msg.sender != peerUID && msg.receiver != peerUID) {\n            return;\n        }\n        if (this.secret) {\n            return;\n        }\n        Log.i(TAG, \"recv msg:\" + msg.content);\n        final IMessage imsg = new IMessage();\n        imsg.timestamp = msg.timestamp;\n        imsg.msgLocalID = msg.msgLocalID;\n        imsg.sender = msg.sender;\n        imsg.receiver = msg.receiver;\n        imsg.setContent(msg.content);\n        imsg.isOutgoing = (msg.sender == this.currentUID);\n        if (imsg.isOutgoing) {\n            imsg.flags |= MessageFlag.MESSAGE_FLAG_ACK;\n        }\n\n        IMessage mm = findMessage(imsg.getUUID());\n        if (mm != null) {\n            Log.i(TAG, \"receive repeat message:\" + imsg.getUUID());\n            //清空消息失败标志位\n            if (imsg.isOutgoing) {\n                int flags = imsg.flags;\n                flags = flags & ~MessageFlag.MESSAGE_FLAG_FAILURE;\n                flags = flags | MessageFlag.MESSAGE_FLAG_ACK;\n                mm.setFlags(flags);\n            }\n            return;\n        }\n\n        if (msg.isSelf) {\n            return;\n        }\n\n        loadUserName(imsg);\n        downloadMessageContent(imsg);\n        updateNotificationDesc(imsg);\n\n        if (imsg.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n            Revoke revoke = (Revoke)imsg.content;\n            IMessage m = findMessage(revoke.msgid);\n            if (m != null) {\n                replaceMessage(m, imsg);\n            }\n        } else {\n            insertMessage(imsg);\n        }\n    }\n\n\n    @Override\n    public void onPeerSecretMessage(IMMessage msg) {\n        if (msg.sender != peerUID && msg.receiver != peerUID) {\n            return;\n        }\n        if (!this.secret) {\n            return;\n        }\n\n        Log.i(TAG, \"recv msg:\" + msg.content);\n        final IMessage imsg = new IMessage();\n        imsg.timestamp = msg.timestamp;\n        imsg.msgLocalID = msg.msgLocalID;\n        imsg.sender = msg.sender;\n        imsg.receiver = msg.receiver;\n        imsg.secret = true;\n        imsg.setContent(msg.content);\n        imsg.isOutgoing = (msg.sender == this.currentUID);\n        if (imsg.isOutgoing) {\n            imsg.flags |= MessageFlag.MESSAGE_FLAG_ACK;\n        }\n\n        if (imsg.getType() == MessageContent.MessageType.MESSAGE_P2P_SESSION) {\n            handleP2PSession(imsg);\n            return;\n        }\n\n        IMessage mm = findMessage(imsg.getUUID());\n        if (mm != null) {\n            Log.i(TAG, \"receive repeat message:\" + imsg.getUUID());\n            //清空消息失败标志位\n            if (imsg.isOutgoing) {\n                int flags = imsg.flags;\n                flags = flags & ~MessageFlag.MESSAGE_FLAG_FAILURE;\n                flags = flags | MessageFlag.MESSAGE_FLAG_ACK;\n                mm.setFlags(flags);\n            }\n            return;\n        }\n        if (msg.isSelf) {\n            return;\n        }\n\n        loadUserName(imsg);\n        downloadMessageContent(imsg);\n        updateNotificationDesc(imsg);\n        if (imsg.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n            Revoke revoke = (Revoke)imsg.content;\n            IMessage m = findMessage(revoke.msgid);\n            if (m != null) {\n                replaceMessage(m, imsg);\n            }\n        } else {\n            insertMessage(imsg);\n        }\n    }\n\n    @Override\n    public void onPeerMessageACK(IMMessage im, int error) {\n        long msgLocalID = im.msgLocalID;\n        long uid = im.receiver;\n        if (peerUID != uid) {\n            return;\n        }\n        Log.i(TAG, \"message ack:\" + error);\n\n        if (error == MessageACK.MESSAGE_ACK_SUCCESS) {\n            if (msgLocalID > 0) {\n                IMessage imsg = findMessage(msgLocalID);\n                if (imsg == null) {\n                    Log.i(TAG, \"can't find msg:\" + msgLocalID);\n                    return;\n                }\n                imsg.setAck(true);\n            } else {\n                MessageContent c = IMessage.fromRaw(im.plainContent);\n                if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                    Revoke r = (Revoke) c;\n                    IMessage imsg = findMessage(r.msgid);\n                    if (imsg == null) {\n                        Log.i(TAG, \"can't find msg:\" + r.msgid);\n                        return;\n                    }\n                    imsg.setContent(r);\n                    updateNotificationDesc(imsg);\n                    adapter.notifyDataSetChanged();\n                }\n            }\n        } else {\n            if (msgLocalID > 0) {\n                IMessage imsg = findMessage(msgLocalID);\n                if (imsg == null) {\n                    Log.i(TAG, \"can't find msg:\" + msgLocalID);\n                    return;\n                }\n                imsg.setFailure(true);\n            } else {\n                MessageContent c = IMessage.fromRaw(im.content);\n                if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                    Toast.makeText(this, \"撤回失败\", Toast.LENGTH_SHORT).show();\n                }\n            }\n\n            IMessage ack = new IMessage();\n            ack.sender = 0;\n            ack.receiver = im.sender;\n            ack.timestamp = now();\n            ack.setContent(ACK.newACK(error));\n            updateNotificationDesc(ack);\n            insertMessage(ack);\n        }\n    }\n\n    @Override\n    public void onPeerMessageFailure(IMMessage im) {\n        long msgLocalID = im.msgLocalID;\n        long uid = im.receiver;\n        if (peerUID != uid) {\n            return;\n        }\n        Log.i(TAG, \"message failure\");\n        if (msgLocalID > 0) {\n            IMessage imsg = findMessage(msgLocalID);\n            if (imsg == null) {\n                Log.i(TAG, \"can't find msg:\" + msgLocalID);\n                return;\n            }\n            imsg.setFailure(true);\n        } else {\n            MessageContent c = IMessage.fromRaw(im.content);\n            if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                Toast.makeText(this, \"撤回失败\", Toast.LENGTH_SHORT).show();\n            }\n        }\n    }\n\n    protected void handleP2PSession(IMessage imsg) {\n\n    }\n\n\n    @Override\n    protected MessageIterator createMessageIterator() {\n        messageDB = PeerMessageDB.getInstance();\n        MessageIterator iter = PeerMessageDB.getInstance().newMessageIterator(peerUID);\n        return iter;\n    }\n\n    @Override\n    protected MessageIterator createForwardMessageIterator(long messageID) {\n        messageDB = PeerMessageDB.getInstance();\n        MessageIterator iter = PeerMessageDB.getInstance().newForwardMessageIterator(peerUID, messageID);\n        return iter;\n    }\n\n    @Override\n    protected MessageIterator createBackwardMessageIterator(long messageID) {\n        messageDB = PeerMessageDB.getInstance();\n        MessageIterator iter = PeerMessageDB.getInstance().newBackwardMessageIterator(peerUID, messageID);\n        return iter;\n    }\n\n    @Override\n    protected MessageIterator createMiddleMessageIterator(long messageID) {\n        messageDB = PeerMessageDB.getInstance();\n        MessageIterator iter = PeerMessageDB.getInstance().newMiddleMessageIterator(peerUID, messageID);\n        return iter;\n    }\n\n\n    @Override\n    protected boolean getMessageOutgoing(IMessage msg) {\n        return (msg.sender == currentUID);\n    }\n\n\n    @Override\n    protected void sendMessage(IMessage imsg) {\n        PeerOutbox.getInstance().sendMessage(imsg);\n    }\n\n\n    @Override\n    protected IMessage newOutMessage(MessageContent content) {\n        IMessage msg = new IMessage();\n        msg.sender = this.currentUID;\n        msg.receiver = this.peerUID;\n        msg.secret = secret;\n        msg.content = content;\n        return msg;\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/activity/CameraActivity.java",
    "content": "package com.beetle.bauhinia.activity;\n\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.graphics.Bitmap;\nimport android.os.Build;\nimport android.os.Bundle;\n\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.Toast;\n\nimport com.cjt2325.cameralibrary.JCameraView;\nimport com.cjt2325.cameralibrary.listener.ClickListener;\nimport com.cjt2325.cameralibrary.listener.ErrorListener;\nimport com.cjt2325.cameralibrary.listener.JCameraListener;\nimport com.cjt2325.cameralibrary.util.DeviceUtil;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\nimport com.beetle.imkit.R;\npublic class CameraActivity extends BaseActivity {\n    private final String TAG = \"goubuli\";\n\n    private JCameraView jCameraView;\n\n    private String cameraDir;//录制过程中的保存路径\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);\n        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n        setContentView(R.layout.activity_camera);\n\n\n        Intent intent = getIntent();\n        String dir = intent.getStringExtra(\"dir\");\n        if (!TextUtils.isEmpty(dir)) {\n            cameraDir = dir;\n        } else {\n            cameraDir = this.getExternalCacheDir().getPath() + File.separator + \"JCamera\";\n        }\n\n        File file = new File(cameraDir);\n        if (!file.exists()) {\n            file.mkdirs();\n        }\n\n        jCameraView = (JCameraView) findViewById(R.id.jcameraview);\n        jCameraView.setSaveVideoPath(cameraDir);\n        jCameraView.setFeatures(JCameraView.BUTTON_STATE_BOTH);\n        jCameraView.setTip(\"轻触拍照，按住摄像\");\n        jCameraView.setMediaQuality(JCameraView.MEDIA_QUALITY_MIDDLE);\n        jCameraView.setErrorLisenter(new ErrorListener() {\n            @Override\n            public void onError() {\n                //错误监听\n                Log.i(TAG, \"camera error\");\n                Intent intent = new Intent();\n                setResult(103, intent);\n                finish();\n            }\n\n            @Override\n            public void AudioPermissionError() {\n                Toast.makeText(CameraActivity.this, \"给点录音权限可以?\", Toast.LENGTH_SHORT).show();\n            }\n        });\n        //JCameraView监听\n        jCameraView.setJCameraLisenter(new JCameraListener() {\n            @Override\n            public void captureSuccess(Bitmap bitmap) {\n                //获取图片bitmap\n                Log.i(TAG, \"bitmap = \" + bitmap.getWidth() + \" \" + bitmap.getHeight());\n                long dataTake = System.currentTimeMillis();\n                String jpegName = cameraDir + File.separator + \"picture_\" + dataTake + \".jpg\";\n\n                saveBitmap(bitmap, jpegName);\n                Intent intent = new Intent();\n                intent.putExtra(\"picture_path\", jpegName);\n                setResult(RESULT_OK, intent);\n                finish();\n            }\n\n            @Override\n            public void recordSuccess(String url, Bitmap firstFrame) {\n                Log.i(TAG, \"url = \" + url + \" length:\" + new File(url).length());\n\n                long dataTake = System.currentTimeMillis();\n                String jpegName = cameraDir + File.separator + \"picture_\" + dataTake + \".jpg\";\n                //获取视频路径\n                boolean r = saveBitmap(firstFrame, jpegName);\n                Log.i(TAG, \"save thumb success:\" + jpegName);\n                if (r) {\n                    Intent intent = new Intent();\n                    intent.putExtra(\"video_path\", url);\n                    intent.putExtra(\"thumbnail_path\", jpegName);\n                    setResult(RESULT_OK, intent);\n                    finish();\n                }\n            }\n        });\n\n        jCameraView.setLeftClickListener(new ClickListener() {\n            @Override\n            public void onClick() {\n                CameraActivity.this.finish();\n            }\n        });\n        jCameraView.setRightClickListener(new ClickListener() {\n            @Override\n            public void onClick() {\n                Toast.makeText(CameraActivity.this,\"Right\",Toast.LENGTH_SHORT).show();\n            }\n        });\n\n        Log.i(TAG, DeviceUtil.getDeviceModel());\n    }\n\n    public void storeByteArray(String fileName, ByteArrayOutputStream byteStream) throws IOException {\n        File file = new File(fileName);\n        FileOutputStream fileOutputStream = new FileOutputStream(file);\n        byteStream.writeTo(fileOutputStream);\n        fileOutputStream.flush();\n        fileOutputStream.close();\n    }\n\n    boolean saveBitmap(Bitmap bitmap, String fileName) {\n        ByteArrayOutputStream os = new ByteArrayOutputStream();\n        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);\n        try {\n            storeByteArray(fileName, os);\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        //全屏显示\n        if (Build.VERSION.SDK_INT >= 19) {\n            View decorView = getWindow().getDecorView();\n            decorView.setSystemUiVisibility(\n                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION\n                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN\n                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION\n                            | View.SYSTEM_UI_FLAG_FULLSCREEN\n                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);\n        } else {\n            View decorView = getWindow().getDecorView();\n            int option = View.SYSTEM_UI_FLAG_FULLSCREEN;\n            decorView.setSystemUiVisibility(option);\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        jCameraView.onResume();\n    }\n\n    @Override\n    protected void onPause() {\n        super.onPause();\n        jCameraView.onPause();\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/activity/LocationPickerActivity.java",
    "content": "package com.beetle.bauhinia.activity;\r\n\r\nimport android.animation.ObjectAnimator;\r\nimport android.content.Context;\r\nimport android.content.Intent;\r\nimport android.location.Location;\r\nimport android.os.Bundle;\r\nimport android.os.Handler;\r\nimport android.os.Message;\r\nimport android.text.TextUtils;\r\nimport android.view.Menu;\r\nimport android.view.MenuItem;\r\nimport android.view.MotionEvent;\r\nimport android.view.View;\r\nimport android.view.animation.AccelerateInterpolator;\r\nimport android.view.animation.BounceInterpolator;\r\nimport android.widget.TextView;\r\nimport android.widget.Toast;\r\n\r\nimport com.amap.api.location.AMapLocation;\r\nimport com.amap.api.location.AMapLocationClient;\r\nimport com.amap.api.location.AMapLocationClientOption;\r\nimport com.amap.api.location.AMapLocationListener;\r\n//import com.amap.api.location.LocationManagerProxy;\r\n//import com.amap.api.location.LocationProviderProxy;\r\nimport com.amap.api.maps2d.AMap;\r\nimport com.amap.api.maps2d.CameraUpdateFactory;\r\nimport com.amap.api.maps2d.LocationSource;\r\nimport com.amap.api.maps2d.MapView;\r\nimport com.amap.api.maps2d.model.BitmapDescriptorFactory;\r\nimport com.amap.api.maps2d.model.LatLng;\r\nimport com.amap.api.maps2d.model.MyLocationStyle;\r\nimport com.amap.api.services.core.AMapException;\r\nimport com.amap.api.services.core.LatLonPoint;\r\nimport com.amap.api.services.geocoder.GeocodeResult;\r\nimport com.amap.api.services.geocoder.GeocodeSearch;\r\nimport com.amap.api.services.geocoder.RegeocodeQuery;\r\nimport com.amap.api.services.geocoder.RegeocodeResult;\r\nimport com.beetle.imkit.R;\r\n\r\nimport java.lang.ref.WeakReference;\r\n\r\n/**\r\n * AMapV1地图demo总汇\r\n */\r\npublic class LocationPickerActivity extends BaseActivity implements GeocodeSearch.OnGeocodeSearchListener, AMapLocationListener, LocationSource {\r\n    private static final int MSG_LOCATION_TIMEOUT = 1000;\r\n    private MapView mapView;\r\n\r\n    private AMap aMap;\r\n    private View pin;\r\n    private TextView label;\r\n    private GeocodeSearch mGeocodeSearch;\r\n\r\n    private OnLocationChangedListener mListener;\r\n    private AMapLocationClient mlocationClient;\r\n    private AMapLocationClientOption mLocationOption;\r\n\r\n    double longitude = 0;\r\n    double latitude = 0;\r\n    String address;\r\n\r\n    private boolean isCameraChanging = false;\r\n\r\n    public static Intent newIntent(Context context) {\r\n        Intent intent = new Intent();\r\n        intent.setClass(context, LocationPickerActivity.class);\r\n        return intent;\r\n    }\r\n\r\n    @Override\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        super.onCreate(savedInstanceState);\r\n        setContentView(R.layout.location_picker);\r\n\r\n        mapView = (MapView) findViewById(R.id.map);\r\n        mapView.onCreate(savedInstanceState);// 必须要写\r\n        aMap = mapView.getMap();\r\n\r\n        label = (TextView) findViewById(R.id.label);\r\n        pin = findViewById(R.id.pin);\r\n        aMap.setOnMapTouchListener(new AMap.OnMapTouchListener() {\r\n            @Override\r\n            public void onTouch(MotionEvent motionEvent) {\r\n                if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) {\r\n                    if (!isCameraChanging) {\r\n                        isCameraChanging = true;\r\n                        pinUp();\r\n                        setLabel(null);\r\n                    }\r\n                } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {\r\n                    isCameraChanging = false;\r\n                    pinDown();\r\n                    latitude = aMap.getCameraPosition().target.latitude;\r\n                    longitude = aMap.getCameraPosition().target.longitude;\r\n                    queryLocation();\r\n                }\r\n            }\r\n        });\r\n\r\n        mGeocodeSearch = new GeocodeSearch(this);\r\n        mGeocodeSearch.setOnGeocodeSearchListener(this);\r\n\r\n        setUpMap();\r\n    }\r\n\r\n    @Override\r\n    public boolean onCreateOptionsMenu(Menu menu) {\r\n        getMenuInflater().inflate(R.menu.location_picker, menu);\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean onOptionsItemSelected(MenuItem item) {\r\n        int id = item.getItemId();\r\n        if (id == R.id.action_send) {\r\n            if (longitude == 0.0 && latitude == 0.0) {\r\n                return true;\r\n            }\r\n\r\n            Intent intent = new Intent();\r\n            intent.putExtra(\"longitude\", (float) longitude);\r\n            intent.putExtra(\"latitude\", (float) latitude);\r\n            intent.putExtra(\"address\", address);\r\n            setResult(RESULT_OK, intent);\r\n\r\n            finish();\r\n            return true;\r\n        }\r\n        return super.onOptionsItemSelected(item);\r\n    }\r\n\r\n    /**\r\n     * 方法必须重写\r\n     */\r\n    @Override\r\n    protected void onResume() {\r\n        super.onResume();\r\n        mapView.onResume();\r\n    }\r\n\r\n    /**\r\n     * 方法必须重写\r\n     */\r\n    @Override\r\n    protected void onPause() {\r\n        mapView.onPause();\r\n        super.onPause();\r\n    }\r\n\r\n    /**\r\n     * 方法必须重写\r\n     */\r\n    @Override\r\n    protected void onSaveInstanceState(Bundle outState) {\r\n        super.onSaveInstanceState(outState);\r\n        mapView.onSaveInstanceState(outState);\r\n    }\r\n\r\n    /**\r\n     * 方法必须重写\r\n     */\r\n    @Override\r\n    protected void onDestroy() {\r\n        mapView.onDestroy();\r\n        super.onDestroy();\r\n    }\r\n\r\n    private void setLocation(double latitude, double longitude, String address) {\r\n        this.latitude = latitude;\r\n        this.longitude = longitude;\r\n        this.address = address;\r\n\r\n        LatLng latLng = new LatLng(latitude, longitude);\r\n        aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15));\r\n        setLabel(address);\r\n\r\n//        Toast.makeText(this, String.format(Locale.getDefault(), \"lat:%f,lon:%f,%s\", latitude, longitude, address), Toast.LENGTH_SHORT).show();\r\n//        Marker marker = aMap.addMarker(new MarkerOptions()\r\n//                .position(latLng)\r\n//                .title(address)\r\n//                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))\r\n//                .draggable(true));\r\n//        marker.showInfoWindow();\r\n    }\r\n\r\n    @Override\r\n    public void onRegeocodeSearched(RegeocodeResult result, int rCode) {\r\n        if (rCode == AMapException.CODE_AMAP_SUCCESS) {\r\n            if (result != null && result.getRegeocodeAddress() != null\r\n                    && result.getRegeocodeAddress().getFormatAddress() != null) {\r\n                String addressName = result.getRegeocodeAddress().getFormatAddress();\r\n                setLocation(latitude, longitude, addressName);\r\n\r\n            } else {\r\n                setLocation(latitude, longitude, \"\");\r\n            }\r\n        }\r\n\r\n    }\r\n\r\n    @Override\r\n    public void onGeocodeSearched(GeocodeResult geocodeResult, int i) {\r\n    }\r\n\r\n    /**\r\n     * 设置一些amap的属性\r\n     */\r\n    private void setUpMap() {\r\n        aMap.setLocationSource(this);// 设置定位监听\r\n        aMap.getUiSettings().setMyLocationButtonEnabled(true);// 设置默认定位按钮是否显示\r\n        aMap.setMyLocationEnabled(true);// 设置为true表示显示定位层并可触发定位，false表示隐藏定位层并不可触发定位，默认是false\r\n        setupLocationStyle();\r\n    }\r\n\r\n    private void setupLocationStyle(){\r\n\r\n    }\r\n\r\n    /**\r\n     * 激活定位\r\n     */\r\n    @Override\r\n    public void activate(OnLocationChangedListener listener) {\r\n        mListener = listener;\r\n        if (mlocationClient == null) {\r\n            mlocationClient = new AMapLocationClient(this);\r\n            mLocationOption = new AMapLocationClientOption();\r\n            //设置定位监听\r\n            mlocationClient.setLocationListener(this);\r\n            //设置为高精度定位模式\r\n            mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);\r\n            //设置定位参数\r\n            mlocationClient.setLocationOption(mLocationOption);\r\n            // 此方法为每隔固定时间会发起一次定位请求，为了减少电量消耗或网络流量消耗，\r\n            // 注意设置合适的定位时间的间隔（最小间隔支持为2000ms），并且在合适时间调用stopLocation()方法来取消定位请求\r\n            // 在定位结束后，在合适的生命周期调用onDestroy()方法\r\n            // 在单次定位情况下，定位无论成功与否，都无需调用stopLocation()方法移除请求，定位sdk内部会移除\r\n            mlocationClient.startLocation();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 停止定位\r\n     */\r\n    @Override\r\n    public void deactivate() {\r\n        mListener = null;\r\n        if (mlocationClient != null) {\r\n            mlocationClient.stopLocation();\r\n            mlocationClient.onDestroy();\r\n        }\r\n        mlocationClient = null;\r\n    }\r\n\r\n\r\n    /*======== 定位回调 begin ======== */\r\n    @Override\r\n    public void onLocationChanged(AMapLocation aMapLocation) {\r\n        if (aMapLocation != null) {\r\n            setLocation(aMapLocation.getLatitude(), aMapLocation.getLongitude(), aMapLocation.getAddress());\r\n\r\n            if (mListener != null && aMapLocation.getErrorCode() == 0) {\r\n                mListener.onLocationChanged(aMapLocation);// 显示系统小蓝点\r\n                aMap.moveCamera(CameraUpdateFactory.zoomTo(18));\r\n            }\r\n\r\n        } else {\r\n            setLocation(0, 0, null);\r\n        }\r\n        stopLocation();\r\n    }\r\n\r\n    private void stopLocation() {\r\n\r\n        if (mlocationClient != null) {\r\n            mlocationClient.stopLocation();\r\n            mlocationClient.onDestroy();\r\n            mlocationClient = null;\r\n        }\r\n\r\n    }\r\n\r\n    private void queryLocation() {\r\n        // 第一个参数表示一个Latlng，第二参数表示范围多少米，第三个参数表示是火系坐标系还是GPS原生坐标系\r\n        RegeocodeQuery query = new RegeocodeQuery(new LatLonPoint(latitude, longitude), 200, GeocodeSearch.AMAP);\r\n        mGeocodeSearch.getFromLocationAsyn(query);// 设置同步逆地理编码请求\r\n    }\r\n\r\n    private void pinUp() {\r\n        ObjectAnimator anim = ObjectAnimator.ofFloat(pin, \"TranslationY\", -getPinY());\r\n        anim.setDuration(500);\r\n        anim.setInterpolator(new AccelerateInterpolator());\r\n        anim.start();\r\n    }\r\n\r\n    private void pinDown() {\r\n        ObjectAnimator anim = ObjectAnimator.ofFloat(pin, \"TranslationY\", 0);\r\n        anim.setDuration(1000);\r\n        anim.setInterpolator(new BounceInterpolator());\r\n        anim.start();\r\n    }\r\n\r\n    private int getPinY() {\r\n        return pin.getHeight() - getResources().getDimensionPixelOffset(R.dimen.pin_margin);\r\n    }\r\n\r\n    private void setLabel(String address) {\r\n        label.setText(address);\r\n        if (!TextUtils.isEmpty(address)) {\r\n            label.setVisibility(View.VISIBLE);\r\n        } else {\r\n            label.setVisibility(View.GONE);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/activity/MapActivity.java",
    "content": "package com.beetle.bauhinia.activity;\r\n\r\nimport android.content.Context;\r\nimport android.content.Intent;\r\nimport android.os.Bundle;\r\nimport android.view.View;\r\nimport android.widget.TextView;\r\n\r\nimport com.amap.api.location.AMapLocation;\r\nimport com.amap.api.location.AMapLocationListener;\r\nimport com.amap.api.maps2d.AMap;\r\nimport com.amap.api.maps2d.CameraUpdateFactory;\r\nimport com.amap.api.maps2d.MapView;\r\nimport com.amap.api.maps2d.model.BitmapDescriptorFactory;\r\nimport com.amap.api.maps2d.model.LatLng;\r\nimport com.amap.api.maps2d.model.Marker;\r\nimport com.amap.api.maps2d.model.MarkerOptions;\r\nimport com.amap.api.services.core.AMapException;\r\nimport com.amap.api.services.core.LatLonPoint;\r\nimport com.amap.api.services.geocoder.GeocodeResult;\r\nimport com.amap.api.services.geocoder.GeocodeSearch;\r\nimport com.amap.api.services.geocoder.RegeocodeAddress;\r\nimport com.amap.api.services.geocoder.RegeocodeQuery;\r\nimport com.amap.api.services.geocoder.RegeocodeResult;\r\n\r\nimport com.beetle.bauhinia.tools.MapUtil;\r\nimport com.beetle.imkit.R;\r\n/**\r\n * AMapV1地图demo总汇\r\n */\r\npublic class MapActivity extends BaseActivity implements GeocodeSearch.OnGeocodeSearchListener, AMapLocationListener {\r\n    private MapView mapView;\r\n\r\n    private AMap aMap;\r\n    private GeocodeSearch mGeocodeSearch;\r\n\r\n\r\n    TextView addressView;\r\n    TextView townshipView;\r\n    double longitude;\r\n    double latitude;\r\n    String poiname = \"\";\r\n\r\n    public static Intent newIntent(Context context, float longitude, float latitude) {\r\n        Intent intent = new Intent();\r\n        intent.setClass(context, MapActivity.class);\r\n        intent.putExtra(\"longitude\", longitude);\r\n        intent.putExtra(\"latitude\", latitude);\r\n        return intent;\r\n    }\r\n\r\n    @Override\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        super.onCreate(savedInstanceState);\r\n        setContentView(R.layout.chat_location);\r\n\r\n\r\n        Intent intent = getIntent();\r\n        longitude = intent.getFloatExtra(\"longitude\", 0);\r\n        latitude = intent.getFloatExtra(\"latitude\", 0);\r\n\r\n\r\n        mapView = (MapView) findViewById(R.id.map);\r\n        mapView.onCreate(savedInstanceState);// 必须要写\r\n        aMap = mapView.getMap();\r\n\r\n        mGeocodeSearch = new GeocodeSearch(this);\r\n        mGeocodeSearch.setOnGeocodeSearchListener(this);\r\n\r\n\r\n        addressView = (TextView)findViewById(R.id.address);\r\n        townshipView = (TextView)findViewById(R.id.township);\r\n\r\n        setLocation(latitude, longitude, \"\");\r\n\r\n        // 根据经纬度定位\r\n        queryLocation();\r\n    }\r\n\r\n    public void onNavigation(View sender) {\r\n        MapUtil.openMap(this, poiname, longitude, latitude);\r\n    }\r\n\r\n\r\n    /**\r\n     * 方法必须重写\r\n     */\r\n    @Override\r\n    protected void onResume() {\r\n        super.onResume();\r\n        mapView.onResume();\r\n    }\r\n\r\n    /**\r\n     * 方法必须重写\r\n     */\r\n    @Override\r\n    protected void onPause() {\r\n        mapView.onPause();\r\n        super.onPause();\r\n    }\r\n\r\n    /**\r\n     * 方法必须重写\r\n     */\r\n    @Override\r\n    protected void onSaveInstanceState(Bundle outState) {\r\n        super.onSaveInstanceState(outState);\r\n        mapView.onSaveInstanceState(outState);\r\n    }\r\n\r\n    /**\r\n     * 方法必须重写\r\n     */\r\n    @Override\r\n    protected void onDestroy() {\r\n        mapView.onDestroy();\r\n        super.onDestroy();\r\n    }\r\n\r\n    private void queryLocation() {\r\n        // 第一个参数表示一个Latlng，第二参数表示范围多少米，第三个参数表示是火系坐标系还是GPS原生坐标系\r\n        RegeocodeQuery query = new RegeocodeQuery(new LatLonPoint(latitude, longitude), 200, GeocodeSearch.AMAP);\r\n        mGeocodeSearch.getFromLocationAsyn(query);// 设置同步逆地理编码请求\r\n    }\r\n\r\n    private void setLocation(double latitude, double longitude, String address) {\r\n        LatLng latLng = new LatLng(latitude, longitude);\r\n        aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15));\r\n\r\n        Marker marker = aMap.addMarker(new MarkerOptions()\r\n                .position(latLng)\r\n                .title(address)\r\n                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))\r\n                .draggable(true));\r\n        marker.showInfoWindow();\r\n    }\r\n\r\n    @Override\r\n    public void onRegeocodeSearched(RegeocodeResult result, int rCode) {\r\n\r\n        if (rCode == AMapException.CODE_AMAP_SUCCESS) {\r\n            if (result != null && result.getRegeocodeAddress() != null\r\n                    && result.getRegeocodeAddress().getFormatAddress() != null) {\r\n                setLocation(latitude, longitude, result.getRegeocodeAddress().getFormatAddress());\r\n                RegeocodeAddress addr = result.getRegeocodeAddress();\r\n                poiname = addr.getFormatAddress();\r\n                addressView.setText(addr.getFormatAddress());\r\n                townshipView.setText(addr.getTownship());\r\n            }\r\n        }\r\n    }\r\n\r\n    @Override\r\n    public void onGeocodeSearched(GeocodeResult geocodeResult, int i) {\r\n    }\r\n\r\n\r\n\r\n    /*======== 定位回调 begin ======== */\r\n    @Override\r\n    public void onLocationChanged(AMapLocation aMapLocation) {\r\n\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/activity/MessageFileActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.activity;\n\n\nimport android.app.ProgressDialog;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.core.content.FileProvider;\n\nimport com.beetle.bauhinia.activity.BaseActivity;\nimport com.beetle.bauhinia.tools.FileCache;\nimport com.beetle.bauhinia.tools.FileDownloader;\nimport com.beetle.imkit.R;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport org.apache.commons.io.IOUtils;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class MessageFileActivity extends BaseActivity {\n\n    protected static final String TAG = \"imservice\";\n\n    private String url;\n    private String filename;\n    private int size;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_file);\n\n        Intent intent = getIntent();\n        url = intent.getStringExtra(\"url\");\n        filename = intent.getStringExtra(\"filename\");\n        size = intent.getIntExtra(\"size\", 0);\n\n        TextView tv = (TextView)findViewById(R.id.filename);\n        tv.setText(filename);\n\n        ImageView imageView = (ImageView)findViewById(R.id.imageView);\n        if (filename.endsWith(\".doc\") || filename.endsWith(\"docx\")) {\n            imageView.setImageResource(R.drawable.word);\n        } else if (filename.endsWith(\".xls\") || filename.endsWith(\".xlsx\")) {\n            imageView.setImageResource(R.drawable.excel);\n        } else if (filename.endsWith(\".pdf\")) {\n            imageView.setImageResource(R.drawable.pdf);\n        } else {\n            imageView.setImageResource(R.drawable.file);\n        }\n\n        if (!FileCache.getInstance().isCached(url)) {\n            this.download(url);\n        }\n    }\n\n    public void onOpen(View v) {\n        File f = new File(FileCache.getInstance().getCachedFilePath(url));\n        Log.i(TAG, \"open file:\" + filename + \" \" + f.getAbsolutePath());\n        if (f.exists()) {\n            try {\n                openFile(this, filename, f);\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    private void download(final String url) {\n        final ProgressDialog dialog = ProgressDialog.show(this, null, \"正在下载...\");\n        new AsyncTask<Void, Integer, Boolean>() {\n            @Override\n            protected Boolean doInBackground(Void... urls) {\n                try {\n                    OkHttpClient client = new OkHttpClient();\n                    Request request = new Request.Builder().url(url).build();\n                    Response response = client.newCall(request).execute();\n                    if (response.isSuccessful()) {\n                        InputStream inputStream = response.body().byteStream();\n                        FileCache.getInstance().storeFile(url, inputStream);\n                        inputStream.close();\n                        return true;\n                    } else {\n                        return false;\n                    }\n                } catch (IOException e) {\n                    return false;\n                }\n            }\n\n            @Override\n            protected void onPostExecute(Boolean result) {\n                dialog.dismiss();\n                if (!result) {\n                    Toast.makeText(getApplicationContext(), \"下载失败\", Toast.LENGTH_SHORT).show();\n                    return;\n                }\n            }\n\n\n        }.execute();\n    }\n\n\n    public static void openFile(Context context, String filename, File url) throws IOException {\n        Intent intent = new Intent(Intent.ACTION_VIEW);\n\n        File file = url;\n        Uri uri;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            String authority = context.getPackageName() + \".fileprovider\";\n            uri = FileProvider.getUriForFile(context, authority, file);\n            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n        } else {\n            uri = Uri.fromFile(file);\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n\n        // Check what kind of file you are trying to open, by comparing the url with extensions.\n        // When the if condition is matched, plugin sets the correct intent (mime) type,\n        // so Android knew what application to use to open the file\n        if (filename.contains(\".doc\") || filename.contains(\".docx\")) {\n            // Word document\n            intent.setDataAndType(uri, \"application/msword\");\n        } else if(filename.contains(\".pdf\")) {\n            // PDF file\n            intent.setDataAndType(uri, \"application/pdf\");\n        } else if(filename.contains(\".ppt\") || filename.contains(\".pptx\")) {\n            // Powerpoint file\n            intent.setDataAndType(uri, \"application/vnd.ms-powerpoint\");\n        } else if(filename.contains(\".xls\") || filename.contains(\".xlsx\")) {\n            // Excel file\n            intent.setDataAndType(uri, \"application/vnd.ms-excel\");\n        } else if(filename.contains(\".zip\") || filename.contains(\".rar\")) {\n            // WAV audio file\n            intent.setDataAndType(uri, \"application/x-wav\");\n        } else if(filename.contains(\".rtf\")) {\n            // RTF file\n            intent.setDataAndType(uri, \"application/rtf\");\n        } else if(filename.contains(\".wav\") || filename.contains(\".mp3\")) {\n            // WAV audio file\n            intent.setDataAndType(uri, \"audio/x-wav\");\n        } else if(filename.contains(\".gif\")) {\n            // GIF file\n            intent.setDataAndType(uri, \"image/gif\");\n        } else if(filename.contains(\".jpg\") || filename.contains(\".jpeg\") || filename.contains(\".png\")) {\n            // JPG file\n            intent.setDataAndType(uri, \"image/jpeg\");\n        } else if(filename.contains(\".txt\")) {\n            // Text file\n            intent.setDataAndType(uri, \"text/plain\");\n        } else if(filename.contains(\".3gp\") || filename.contains(\".mpg\") || filename.contains(\".mpeg\") || filename.contains(\".mpe\") || filename.contains(\".mp4\") || filename.contains(\".avi\")) {\n            // Video files\n            intent.setDataAndType(uri, \"video/*\");\n        } else {\n            //if you want you can also define the intent type for any other file\n\n            //additionally use else clause below, to manage other unknown extensions\n            //in this case, Android will show all applications installed on the device\n            //so you can choose which application to use\n            intent.setDataAndType(uri, \"*/*\");\n        }\n\n        context.startActivity(intent);\n    }\n\n\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/activity/OverlayActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.activity;\n\nimport android.os.Bundle;\nimport android.text.SpannableString;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.TextView;\nimport com.beetle.imkit.R;\nimport com.beetle.bauhinia.toolbar.emoticon.EmoticonManager;\n\npublic class OverlayActivity extends BaseActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        requestWindowFeature(Window.FEATURE_NO_TITLE);\n\n        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,\n                WindowManager.LayoutParams.FLAG_FULLSCREEN);\n\n        getWindow().addFlags( WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |\n                WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |\n                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |\n                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);\n\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_overlay);\n\n        String text = getIntent().getStringExtra(\"text\");\n        TextView v = (TextView)findViewById(R.id.text);\n        v.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                finish();\n            }\n        });\n\n        if (!TextUtils.isEmpty(text)) {\n            SpannableString s = EmoticonManager.getInstance().getEmoticonStr(text);\n            v.setText(s);\n        }\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/activity/PhotoActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.activity;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.widget.ImageView;\n\nimport com.beetle.imkit.R;\nimport com.squareup.picasso.Picasso;\n\n\n\npublic class PhotoActivity extends BaseActivity {\n    static final String EXTRA_URL = \"im.url\";\n\n    ImageView photo;\n\n    public static Intent newIntent(Context context, String url) {\n        Intent intent = new Intent();\n        intent.setClass(context, PhotoActivity.class);\n        intent.putExtra(EXTRA_URL, url);\n        return intent;\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_photo);\n        try {\n            getActionBar().hide();\n        } catch (Exception ignored) {}\n\n        photo = (ImageView)findViewById(R.id.photo);\n        Picasso.get()\n                .load(getIntent().getStringExtra(EXTRA_URL))\n                .fit()\n                .centerInside()\n                .into(photo);\n    }\n\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        // Inflate the menu; this adds items to the action bar if it is present.\n        getMenuInflater().inflate(R.menu.menu_photo, menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        // Handle action bar item clicks here. The action bar will\n        // automatically handle clicks on the Home/Up button, so long\n        // as you specify a parent activity in AndroidManifest.xml.\n        int id = item.getItemId();\n\n        //noinspection SimplifiableIfStatement\n\n        return super.onOptionsItemSelected(item);\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/activity/PlayerActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.activity;\n\nimport android.app.ProgressDialog;\nimport android.content.Intent;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.text.TextUtils;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.MediaController;\nimport android.widget.Toast;\nimport android.widget.VideoView;\nimport com.beetle.bauhinia.tools.FileCache;\nimport com.beetle.imkit.R;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class PlayerActivity extends BaseActivity {\n\n    private MediaController mediaController;\n    private String videoURL;\n\n    private boolean secret;//是否点对点加密\n    private long sender;//点对点加密消息的发送者\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        requestWindowFeature(Window.FEATURE_NO_TITLE);\n\n        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,\n                WindowManager.LayoutParams.FLAG_FULLSCREEN);\n\n        getWindow().addFlags( WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |\n                WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |\n                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |\n                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);\n\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_player);\n        Intent intent = getIntent();\n\n        String url = intent.getStringExtra(\"url\");\n        if (TextUtils.isEmpty(url)) {\n            finish();\n            return;\n        }\n\n        secret = intent.getBooleanExtra(\"secret\", false);\n        sender = intent.getLongExtra(\"sender\", -1);\n\n        if (secret && sender == -1) {\n            finish();\n            return;\n        }\n\n\n        videoURL = url;\n\n        VideoView vv = (VideoView) findViewById(R.id.video);\n        mediaController = new MediaController(this);\n        vv.setMediaController(mediaController);\n\n\n        if (!FileCache.getInstance().isCached(videoURL)) {\n            download(videoURL);\n        } else {\n            String path = FileCache.getInstance().getCachedFilePath(videoURL);\n            vv.setVideoPath(path);\n            //autoplay\n            vv.start();\n            showControl();\n        }\n    }\n\n    private void showControl() {\n        Handler handler=new Handler();\n        Runnable runnable=new Runnable(){\n            @Override\n            public void run() {\n                mediaController.show();\n            }\n        };\n        handler.postDelayed(runnable, 100);\n    }\n\n    private void download(final String url) {\n        final ProgressDialog dialog = ProgressDialog.show(this, null, \"正在下载...\");\n        new AsyncTask<Void, Integer, Boolean>() {\n            @Override\n            protected Boolean doInBackground(Void... urls) {\n                try {\n                    OkHttpClient client = new OkHttpClient();\n                    Request request = new Request.Builder().url(url).build();\n                    Response response = client.newCall(request).execute();\n                    if (response.isSuccessful()) {\n                        InputStream inputStream = response.body().byteStream();\n                        FileCache.getInstance().storeFile(url, inputStream);\n                        inputStream.close();\n                        return true;\n                    } else {\n                        return false;\n                    }\n                } catch (IOException e) {\n                    return false;\n                }\n            }\n\n            @Override\n            protected void onPostExecute(Boolean result) {\n                dialog.dismiss();\n                if (!result) {\n                    Toast.makeText(getApplicationContext(), \"下载失败\", Toast.LENGTH_SHORT).show();\n                    return;\n                }\n\n                VideoView vv = (VideoView) findViewById(R.id.video);\n                String path = FileCache.getInstance().getCachedFilePath(videoURL);\n                vv.setVideoPath(path);\n                //autoplay\n                vv.start();\n                showControl();\n            }\n        }.execute();\n    }\n\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/activity/WebActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.activity;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.Window;\nimport android.webkit.WebChromeClient;\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\nimport android.widget.Toast;\n\npublic class WebActivity extends BaseActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        getWindow().requestFeature(Window.FEATURE_PROGRESS);\n\n        super.onCreate(savedInstanceState);\n\n        WebView webview = new WebView(this);\n        setContentView(webview);\n\n        Intent intent = getIntent();\n        String url = intent.getStringExtra(\"url\");\n        if (TextUtils.isEmpty(url)) {\n            return;\n        }\n\n        webview.getSettings().setJavaScriptEnabled(true);\n\n        final Activity activity = this;\n        webview.setWebChromeClient(new WebChromeClient() {\n            public void onProgressChanged(WebView view, int progress) {\n                // Activities and WebViews measure progress with different scales.\n                // The progress meter will automatically disappear when we reach 100%\n                activity.setProgress(progress * 1000);\n            }\n        });\n        webview.setWebViewClient(new WebViewClient() {\n            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {\n                Toast.makeText(activity, \"Oh no! \" + description, Toast.LENGTH_SHORT).show();\n            }\n        });\n\n        webview.loadUrl(url);\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/api/IMHttpAPI.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.api;\n\n\nimport com.beetle.bauhinia.api.body.PostDeviceToken;\nimport com.beetle.bauhinia.api.types.Audio;\nimport com.beetle.bauhinia.api.types.File;\nimport com.beetle.bauhinia.api.types.Image;\nimport com.beetle.bauhinia.api.types.Supporter;\nimport com.google.gson.Gson;\n\nimport retrofit.RequestInterceptor;\nimport retrofit.RestAdapter;\n\nimport retrofit.converter.GsonConverter;\nimport retrofit.http.Body;\nimport retrofit.http.GET;\nimport retrofit.http.Header;\nimport retrofit.http.Multipart;\nimport retrofit.http.POST;\nimport retrofit.http.Part;\nimport retrofit.http.Query;\nimport retrofit.mime.TypedFile;\nimport rx.Observable;\n\n/**\n * Created by tsung on 10/10/14.\n */\npublic class IMHttpAPI {\n    public static final String API_URL = \"https://api.gobelieve.io/v2\";\n\n    private static IMHttp newIMHttp() {\n        RestAdapter adapter = new RestAdapter.Builder()\n                .setEndpoint(apiURL)\n                .setConverter(new GsonConverter(new Gson()))\n                .setRequestInterceptor(new RequestInterceptor() {\n                    @Override\n                    public void intercept(RequestFacade request) {\n                        if (IMHttpAPI.accessToken != null && !IMHttpAPI.accessToken.equals(\"\")) {\n                            request.addHeader(\"Authorization\", \"Bearer \" + IMHttpAPI.accessToken);\n                        }\n                    }\n                })\n                .build();\n\n        return adapter.create(IMHttp.class);\n    }\n\n    static final Object monitor = new Object();\n    static IMHttp singleton;\n\n    public static IMHttp Singleton() {\n        if (singleton == null) {\n            synchronized (monitor) {\n                if (singleton == null) {\n                    singleton = newIMHttp();\n                }\n            }\n        }\n\n        return singleton;\n    }\n\n    private static String apiURL = API_URL;\n    private static String accessToken;\n\n    public static void setAPIURL(String url) {\n        apiURL = url;\n    }\n\n    public static void setToken(String token) {\n        accessToken = token;\n    }\n\n\n    public interface IMHttp {\n        @POST(\"/device/bind\")\n        Observable<Object> bindDeviceToken(@Body PostDeviceToken token);\n\n        @POST(\"/device/unbind\")\n        Observable<Object> unBindDeviceToken(@Body PostDeviceToken token);\n\n        @POST(\"/images\")\n        Observable<Image> postImages(@Header(\"Content-Type\") String contentType, @Body TypedFile file);\n\n        @POST(\"/audios\")\n        Observable<Audio> postAudios(@Header(\"Content-Type\") String contentType, @Body TypedFile file);\n\n        @Multipart\n        @POST(\"/files\")\n        Observable<File> postFile(@Part(\"file\") TypedFile file);\n\n        @GET(\"/supporters\")\n        Observable<Supporter> getSupporter(@Query(\"store_id\") long storeId);\n    };\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/api/body/PostDeviceToken.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.api.body;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by houxh on 15/2/2.\n */\npublic class PostDeviceToken {\n    @SerializedName(\"ng_device_token\")\n    public String deviceToken;\n\n    @SerializedName(\"xg_device_token\")\n    public String xgDeviceToken;\n\n    @SerializedName(\"xm_device_token\")\n    public String xmDeviceToken;\n\n    @SerializedName(\"hw_device_token\")\n    public String hwDeviceToken;\n\n    @SerializedName(\"gcm_device_token\")\n    public String gcmDeviceToken;\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/api/types/Audio.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.api.types;\n\n/**\n * Created by tsung on 10/10/14.\n */\npublic class Audio extends Media{\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/api/types/File.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.api.types;\n\npublic class File extends Media{\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/api/types/Image.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.api.types;\n\n/**\n * Created by tsung on 10/10/14.\n */\npublic class Image extends Media{\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/api/types/Media.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.api.types;\n\nimport com.google.gson.annotations.SerializedName;\n\n/**\n * Created by tsung on 10/10/14.\n */\npublic class Media {\n    public String src;\n    @SerializedName(\"src_url\")\n    public String srcUrl;\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/api/types/Supporter.java",
    "content": "package com.beetle.bauhinia.api.types;\n\npublic class Supporter {\n    public long id;\n    public long appid;\n    public String name;\n    public String appname;\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/handler/CustomerMessageHandler.java",
    "content": "package com.beetle.bauhinia.handler;\nimport android.text.TextUtils;\n\nimport com.beetle.bauhinia.db.CustomerMessageDB;\nimport com.beetle.bauhinia.db.ICustomerMessage;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.MessageFlag;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Revoke;\nimport com.beetle.im.CustomerMessage;\n\n/**\n * Created by houxh on 16/1/18.\n */\npublic class CustomerMessageHandler implements com.beetle.im.CustomerMessageHandler {\n\n    private static CustomerMessageHandler instance = new CustomerMessageHandler();\n\n    public static CustomerMessageHandler getInstance() {\n        return instance;\n    }\n\n    //当前用户id\n    private long uid;\n    private long appid;\n    public void setUID(long uid) {\n        this.uid = uid;\n    }\n    public void setAppId(long appid) {\n        this.appid = appid;\n    }\n\n    private void repairFailureMessage(String uuid) {\n        //纠正消息标志位\n        CustomerMessageDB db = CustomerMessageDB.getInstance();\n        if (!TextUtils.isEmpty(uuid)) {\n            IMessage m = db.getMessage(uuid);\n            if (m == null) {\n                return;\n            }\n\n            if ((m.flags & MessageFlag.MESSAGE_FLAG_FAILURE) != 0 || (m.flags & MessageFlag.MESSAGE_FLAG_ACK) == 0) {\n                m.flags = m.flags & (~MessageFlag.MESSAGE_FLAG_FAILURE);\n                m.flags = m.flags | MessageFlag.MESSAGE_FLAG_ACK;\n                db.updateFlag(m.msgLocalID, m.flags);\n            }\n        }\n    }\n\n\n\n    @Override\n    public boolean handleMessage(CustomerMessage msg) {\n        CustomerMessageDB db = CustomerMessageDB.getInstance();\n        ICustomerMessage imsg = new ICustomerMessage();\n\n        imsg.timestamp = msg.timestamp;\n        imsg.senderAppID = msg.senderAppID;\n        imsg.sender = msg.sender;\n\n        imsg.receiverAppID = msg.receiverAppID;\n        imsg.receiver = msg.receiver;\n        imsg.setContent(msg.content);\n\n        long peerAppID;\n        long peer;\n        if (msg.senderAppID == this.appid && msg.sender == this.uid) {\n            imsg.flags = MessageFlag.MESSAGE_FLAG_ACK;\n            peerAppID = msg.receiverAppID;\n            peer = msg.receiver;\n        } else {\n            peerAppID = msg.senderAppID;\n            peer = msg.sender;\n        }\n\n        if (msg.isSelf) {\n            repairFailureMessage(imsg.getUUID());\n            return true;\n        } else if (imsg.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n            Revoke revoke = (Revoke) imsg.content;\n            long msgLocalID = db.getMessageId(revoke.msgid);\n            if (msgLocalID > 0) {\n                db.removeMessage(msgLocalID);\n            }\n            return true;\n        } else {\n            boolean r = db.insertMessage(imsg, peerAppID, peer);\n            msg.msgLocalID = imsg.msgLocalID;\n            return r;\n        }\n    }\n\n    @Override\n    public boolean handleMessageACK(CustomerMessage msg) {\n        CustomerMessageDB db = CustomerMessageDB.getInstance();\n        long msgLocalID = msg.msgLocalID;\n        if (msgLocalID == 0) {\n            MessageContent c = IMessage.fromRaw(msg.content);\n            if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                Revoke r = (Revoke)c;\n                long revokedMsgId = db.getMessageId(r.msgid);\n                if (revokedMsgId > 0) {\n                    db.updateContent(revokedMsgId, msg.content);\n                    db.removeMessageIndex(revokedMsgId);\n                }\n            }\n            return true;\n        } else {\n            return db.acknowledgeMessage(msg.msgLocalID);\n        }\n    }\n\n    @Override\n    public boolean handleMessageFailure(CustomerMessage msg) {\n        CustomerMessageDB db = CustomerMessageDB.getInstance();\n        return db.markMessageFailure(msg.msgLocalID);\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/handler/GroupMessageHandler.java",
    "content": "package com.beetle.bauhinia.handler;\n\nimport android.text.TextUtils;\n\nimport com.beetle.bauhinia.db.GroupMessageDB;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.MessageFlag;\nimport com.beetle.bauhinia.db.message.GroupNotification;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Readed;\nimport com.beetle.bauhinia.db.message.Revoke;\nimport com.beetle.bauhinia.db.message.Tag;\nimport com.beetle.im.IMMessage;\nimport com.beetle.im.MessageACK;\n\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * Created by houxh on 15/3/21.\n */\npublic class GroupMessageHandler implements com.beetle.im.GroupMessageHandler {\n\n    private static GroupMessageHandler instance = new GroupMessageHandler();\n\n    public static GroupMessageHandler getInstance() {\n        return instance;\n    }\n\n\n    public static int now() {\n        Date date = new Date();\n        long t = date.getTime();\n        return (int)(t/1000);\n    }\n\n    //当前用户id\n    private long uid;\n\n    public void setUID(long uid) {\n        this.uid = uid;\n    }\n\n    private void repairFailureMessage(String uuid) {\n        //消息由本设备发出，则不需要重新入库，用于纠正消息标志位\n        GroupMessageDB db = GroupMessageDB.getInstance();\n        if (!TextUtils.isEmpty(uuid)) {\n            IMessage m = db.getMessage(uuid);\n            if (m == null) {\n                return;\n            }\n\n            if ((m.flags & MessageFlag.MESSAGE_FLAG_FAILURE) != 0 || (m.flags & MessageFlag.MESSAGE_FLAG_ACK) == 0) {\n                m.flags = m.flags & (~MessageFlag.MESSAGE_FLAG_FAILURE);\n                m.flags = m.flags | MessageFlag.MESSAGE_FLAG_ACK;\n                db.updateFlag(m.msgLocalID, m.flags);\n            }\n        }\n    }\n    @Override\n    public boolean handleMessages(List<IMMessage> msgs) {\n        GroupMessageDB db = GroupMessageDB.getInstance();\n\n        ArrayList<IMessage> imsgs = new ArrayList<>();\n        ArrayList<IMMessage> insertedMsgs = new ArrayList<>();\n\n        ArrayList<IMessage> controlMsgs = new ArrayList<>();\n        ArrayList<IMMessage> cmsgs = new ArrayList<>();\n\n        for (IMMessage msg : msgs) {\n            IMessage imsg = new IMessage();\n            imsg.sender = msg.sender;\n            imsg.receiver = msg.receiver;\n            imsg.timestamp = msg.timestamp;\n\n            if (msg.isGroupNotification) {\n                assert(msg.sender == 0);\n                GroupNotification groupNotification = GroupNotification.newGroupNotification(msg.content);\n                imsg.receiver = groupNotification.groupID;\n                imsg.timestamp = groupNotification.timestamp;\n                msg.receiver = groupNotification.groupID;\n                msg.timestamp = groupNotification.timestamp;\n                imsg.setContent(groupNotification);\n            } else {\n                imsg.setContent(msg.content);\n            }\n\n            if (msg.sender == this.uid) {\n                imsg.flags = MessageFlag.MESSAGE_FLAG_ACK;\n                imsg.isOutgoing = true;\n            }\n\n            //避免在observer中重复构造content对象\n            msg.contentObj = imsg.content;\n\n            if (msg.isSelf) {\n                assert(msg.sender == uid);\n                repairFailureMessage(imsg.getUUID());\n            } else if (imsg.getType() == MessageContent.MessageType.MESSAGE_REVOKE ||\n                    imsg.getType() == MessageContent.MessageType.MESSAGE_TAG ||\n                    imsg.getType() == MessageContent.MessageType.MESSAGE_READED) {\n                controlMsgs.add(imsg);\n                cmsgs.add(msg);\n            } else {\n                imsgs.add(imsg);\n                insertedMsgs.add(msg);\n            }\n        }\n\n        if (imsgs.size() > 0) {\n            db.insertMessages(imsgs);\n        }\n\n        for (int i = 0; i < insertedMsgs.size(); i++) {\n            IMMessage msg = insertedMsgs.get(i);\n            IMessage m = imsgs.get(i);\n            msg.msgLocalID = m.msgLocalID;\n        }\n\n        for (int i = 0; i < controlMsgs.size(); i++) {\n            IMessage imsg = controlMsgs.get(i);\n            IMMessage im = cmsgs.get(i);\n\n            if (imsg.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                Revoke revoke = (Revoke) imsg.content;\n                long msgLocalID = db.getMessageId(revoke.msgid);\n                if (msgLocalID > 0) {\n                    db.updateContent(msgLocalID, imsg.content.getRaw());\n                    db.removeMessageIndex(msgLocalID, imsg.receiver);\n                }\n            } else if (imsg.getType() == MessageContent.MessageType.MESSAGE_TAG) {\n                Tag tag = (Tag) imsg.content;\n                long msgLocalID = db.getMessageId(tag.msgid);\n                if (msgLocalID > 0){\n                    if (!TextUtils.isEmpty(tag.addTag)) {\n                        db.addMessageTag(msgLocalID, tag.addTag);\n                    } else if (!TextUtils.isEmpty(tag.deleteTag)) {\n                        db.removeMessageTag(msgLocalID, tag.deleteTag);\n                    }\n                }\n            } else if (imsg.getType() == MessageContent.MessageType.MESSAGE_READED) {\n                Readed readed = (Readed) imsg.content;\n                long msgLocalID = GroupMessageDB.getInstance().getMessageId(readed.msgid);\n                if (msgLocalID > 0) {\n                    if (imsg.isOutgoing) {\n                        int rowsAffected = GroupMessageDB.getInstance().markMessageReaded(msgLocalID);\n                        im.decrementUnread = rowsAffected > 0;\n                    } else {\n                        GroupMessageDB.getInstance().addMessageReader(msgLocalID, imsg.sender);\n                    }\n                }\n            }\n        }\n\n        return true;\n    }\n\n    public boolean handleMessageACK(IMMessage im, int error) {\n        long msgLocalID = im.msgLocalID;\n        long gid = im.receiver;\n        GroupMessageDB db = GroupMessageDB.getInstance();\n        if (error == MessageACK.MESSAGE_ACK_SUCCESS) {\n            if (msgLocalID == 0) {\n                MessageContent c = IMessage.fromRaw(im.content);\n                if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                    Revoke r = (Revoke) c;\n                    long revokedMsgId = db.getMessageId(r.msgid);\n                    if (revokedMsgId > 0) {\n                        db.updateContent(revokedMsgId, im.content);\n                        db.removeMessageIndex(revokedMsgId, gid);\n                    }\n                } else if (c.getType() == MessageContent.MessageType.MESSAGE_TAG) {\n                    Tag tag = (Tag) c;\n                    long msgId = db.getMessageId(tag.msgid);\n                    if (msgId > 0) {\n                        if (!TextUtils.isEmpty(tag.addTag)) {\n                            db.addMessageTag(msgId, tag.addTag);\n                        } else if (!TextUtils.isEmpty(tag.deleteTag)) {\n                            db.removeMessageTag(msgId, tag.deleteTag);\n                        }\n                    }\n                }\n            } else {\n                db.acknowledgeMessage(msgLocalID);\n            }\n        } else {\n            if (msgLocalID > 0) {\n                db.markMessageFailure(msgLocalID);\n            }\n        }\n        return true;\n    }\n\n    public boolean handleMessageFailure(IMMessage im) {\n        long msgLocalID = im.msgLocalID;\n        if (msgLocalID > 0) {\n            GroupMessageDB db = GroupMessageDB.getInstance();\n            db.markMessageFailure(msgLocalID);\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/handler/PeerMessageHandler.java",
    "content": "package com.beetle.bauhinia.handler;\n\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.beetle.bauhinia.db.GroupMessageDB;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.MessageFlag;\nimport com.beetle.bauhinia.db.PeerMessageDB;\nimport com.beetle.bauhinia.db.message.ACK;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Readed;\nimport com.beetle.bauhinia.db.message.Revoke;\nimport com.beetle.bauhinia.db.message.Secret;\nimport com.beetle.im.IMMessage;\nimport com.beetle.im.MessageACK;\n\n\n\nimport java.util.Date;\n\n/**\n * Created by houxh on 14-7-22.\n */\npublic class PeerMessageHandler implements com.beetle.im.PeerMessageHandler {\n    private static PeerMessageHandler instance = new PeerMessageHandler();\n    public static PeerMessageHandler getInstance() {\n        return instance;\n    }\n\n    //当前用户id\n    private long uid;\n\n    public void setUID(long uid) {\n        this.uid = uid;\n    }\n\n    private void repaireFailureMessage(String uuid) {\n        PeerMessageDB db = PeerMessageDB.getInstance();\n        if (!TextUtils.isEmpty(uuid)) {\n            IMessage m = db.getMessage(uuid);\n            if (m == null) {\n                return;\n            }\n\n            if ((m.flags & MessageFlag.MESSAGE_FLAG_FAILURE) != 0 || (m.flags & MessageFlag.MESSAGE_FLAG_ACK) == 0) {\n                m.flags = m.flags & (~MessageFlag.MESSAGE_FLAG_FAILURE);\n                m.flags = m.flags | MessageFlag.MESSAGE_FLAG_ACK;\n                db.updateFlag(m.msgLocalID, m.flags);\n            }\n        }\n    }\n\n    public boolean handleMessage(IMMessage msg) {\n        IMessage imsg = new IMessage();\n        imsg.timestamp = msg.timestamp;\n        imsg.sender = msg.sender;\n        imsg.receiver = msg.receiver;\n        imsg.setContent(msg.content);\n        imsg.secret = false;\n        if (this.uid == msg.sender) {\n            imsg.flags = MessageFlag.MESSAGE_FLAG_ACK;\n            imsg.isOutgoing = true;\n        }\n\n        msg.secret = imsg.secret;\n        msg.contentObj = imsg.content;\n\n        if (imsg.content.getGroupId() > 0) {\n            msg.groupID = imsg.content.getGroupId();\n            return true;\n        }\n\n        PeerMessageDB db = PeerMessageDB.getInstance();\n        if (msg.isSelf) {\n            assert (msg.sender == uid);\n            //消息由本设备发出，则不需要重新入库，用于纠正消息标志位\n            repaireFailureMessage(imsg.getUUID());\n            return true;\n        } else if (imsg.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n            Revoke revoke = (Revoke) imsg.content;\n\n            long msgLocalID = db.getMessageId(revoke.msgid);\n            if (msgLocalID > 0) {\n                db.updateContent(msgLocalID, msg.content);\n                db.removeMessageIndex(msgLocalID);\n            }\n            return true;\n        } else if (imsg.getType() == MessageContent.MessageType.MESSAGE_READED) {\n            Readed readed = (Readed)imsg.content;\n            long msgLocalID = db.getMessageId(readed.msgid);\n            if (msgLocalID > 0) {\n                int rowsAffected = db.markMessageReaded(msgLocalID);\n                msg.decrementUnread = rowsAffected > 0;\n            }\n            return true;\n        } else {\n            long uid = this.uid == msg.sender ? msg.receiver : msg.sender;\n            Log.i(\"goubuli\", \"inserts text message111:\" + msg.content);\n            boolean r = db.insertMessage(imsg, uid);\n            msg.msgLocalID = imsg.msgLocalID;\n            return r;\n        }\n    }\n\n    public boolean handleMessageACK(IMMessage im, int error) {\n        long msgLocalID = im.msgLocalID;\n        PeerMessageDB db = PeerMessageDB.getInstance();\n\n        if (error == MessageACK.MESSAGE_ACK_SUCCESS) {\n            if (msgLocalID == 0) {\n                String content = (im.plainContent != null) ? im.plainContent : im.content;\n                MessageContent c = IMessage.fromRaw(content);\n                if (c.getType() == MessageContent.MessageType.MESSAGE_REVOKE) {\n                    Revoke r = (Revoke) c;\n                    long revokedMsgId = db.getMessageId(r.msgid);\n                    if (revokedMsgId > 0) {\n                        db.updateContent(revokedMsgId, content);\n                        db.removeMessageIndex(revokedMsgId);\n                    }\n                } else if (c.getType() == MessageContent.MessageType.MESSAGE_READED) {\n                    Readed r = (Readed)c;\n                    if (c.getGroupId() > 0) {\n                        long msgId = GroupMessageDB.getInstance().getMessageId(r.msgid);\n                        if (msgId > 0) {\n                            GroupMessageDB.getInstance().markMessageReaded(msgId);\n                        }\n                    } else {\n                        long msgId = db.getMessageId(r.msgid);\n                        if (msgId > 0) {\n                            db.markMessageReaded(msgId);\n                        }\n                    }\n                }\n            } else {\n                db.acknowledgeMessage(msgLocalID);\n            }\n        } else {\n            IMessage ack = new IMessage();\n            ack.sender = 0;\n            ack.receiver = im.sender;\n            ack.timestamp = now();\n            ack.setContent(ACK.newACK(error));\n            db.insertMessage(ack, im.receiver);\n            if (msgLocalID > 0) {\n                db.markMessageFailure(msgLocalID);\n            }\n        }\n        return true;\n    }\n\n    public static int now() {\n        Date date = new Date();\n        long t = date.getTime();\n        return (int)(t/1000);\n    }\n\n\n    public boolean handleMessageFailure(IMMessage im) {\n        long msgLocalID = im.msgLocalID;\n        if (msgLocalID > 0) {\n            PeerMessageDB db = PeerMessageDB.getInstance();\n            db.markMessageFailure(msgLocalID);\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/handler/SyncKeyHandler.java",
    "content": "package com.beetle.bauhinia.handler;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.text.TextUtils;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.HashMap;\n\n/**\n * Created by houxh on 2016/11/2.\n */\n\npublic class SyncKeyHandler implements com.beetle.im.SyncKeyHandler {\n\n    private String name;\n    private Context context;\n\n    private long syncKey;\n    private HashMap<Long, Long> groupSyncKeys = new HashMap<Long, Long>();\n\n    //preference file name\n    public SyncKeyHandler(Context context, String name) {\n        this.name = name;\n        this.context = context;\n    }\n\n    public long getSyncKey() {\n        return syncKey;\n    }\n\n    public HashMap<Long, Long> getSuperGroupSyncKeys() {\n        return this.groupSyncKeys;\n    }\n\n    public boolean saveSyncKey(long syncKey) {\n        this.syncKey = syncKey;\n        return this.save();\n    }\n\n    public boolean saveGroupSyncKey(long groupID, long syncKey) {\n        groupSyncKeys.put(groupID, syncKey);\n        return this.save();\n    }\n\n    public void load() {\n        SharedPreferences pref = context.getSharedPreferences(name, Context.MODE_PRIVATE);\n        String s = pref.getString(\"sync_key\", \"\");\n\n        if (TextUtils.isEmpty(s)) {\n            return;\n        }\n        try {\n            JSONObject obj = new JSONObject(s);\n            this.syncKey = obj.getLong(\"sync_key\");\n\n            this.groupSyncKeys = new HashMap<Long, Long>();\n            JSONArray groups = obj.getJSONArray(\"groups\");\n            for (int i = 0; i < groups.length(); i++) {\n                JSONObject group = groups.getJSONObject(i);\n                groupSyncKeys.put(group.getLong(\"group_id\"), group.getLong(\"sync_key\"));\n            }\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private boolean save() {\n        SharedPreferences pref = context.getSharedPreferences(name, Context.MODE_PRIVATE);\n        SharedPreferences.Editor editor = pref.edit();\n        try {\n            JSONArray groups = new JSONArray();\n            for (HashMap.Entry<Long, Long> e : groupSyncKeys.entrySet()) {\n                JSONObject t = new JSONObject();\n                t.put(\"group_id\", e.getKey());\n                t.put(\"sync_key\", e.getValue());\n                groups.put(t);\n            }\n\n            JSONObject obj = new JSONObject();\n            obj.put(\"groups\", groups);\n            obj.put(\"sync_key\", syncKey);\n            editor.putString(\"sync_key\", obj.toString());\n            editor.commit();\n            return true;\n        } catch (JSONException e) {\n            e.printStackTrace();\n            return false;\n        }\n\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/outbox/CustomerOutbox.java",
    "content": "/*\n  Copyright (c) 2014-2019, GoBelieve\n    All rights reserved.\n\n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.outbox;\nimport com.beetle.bauhinia.db.CustomerMessageDB;\nimport com.beetle.bauhinia.db.ICustomerMessage;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.*;\nimport com.beetle.im.CustomerMessage;\nimport com.beetle.im.IMService;\n\n\n/**\n * Created by houxh on 16/1/18.\n */\npublic class CustomerOutbox extends Outbox {\n    private static CustomerOutbox instance = new CustomerOutbox();\n    public static CustomerOutbox getInstance() {\n        return instance;\n    }\n\n    @Override\n    protected void updateMessageContent(long id, String content) {\n        CustomerMessageDB.getInstance().updateContent(id, content);\n    }\n\n    @Override\n    protected void markMessageFailure(IMessage msg) {\n        CustomerMessageDB.getInstance().markMessageFailure(msg.msgLocalID);\n    }\n\n    @Override\n    protected void sendRawMessage(IMessage imsg, String raw) {\n        ICustomerMessage cm = (ICustomerMessage)imsg;\n\n        CustomerMessage msg = new CustomerMessage();\n        msg.msgLocalID = imsg.msgLocalID;\n        msg.senderAppID = cm.senderAppID;\n        msg.sender = cm.sender;\n        msg.receiverAppID = cm.receiverAppID;\n        msg.receiver = cm.receiver;\n        msg.content = raw;\n\n        IMService im = IMService.getInstance();\n        im.sendCustomerMessageAsync(msg);\n    }\n\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/outbox/GroupOutbox.java",
    "content": "/*\n  Copyright (c) 2014-2019, GoBelieve\n    All rights reserved.\n\n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.outbox;\n\nimport com.beetle.bauhinia.db.GroupMessageDB;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.*;\nimport com.beetle.im.IMMessage;\nimport com.beetle.im.IMService;\n\n\n/**\n * Created by houxh on 14-12-3.\n */\npublic class GroupOutbox extends Outbox{\n    private static GroupOutbox instance = new GroupOutbox();\n    public static GroupOutbox getInstance() {\n        return instance;\n    }\n\n    @Override\n    protected void sendRawMessage(IMessage imsg, String raw) {\n        IMMessage msg = new IMMessage();\n        msg.sender = imsg.sender;\n        msg.receiver = imsg.receiver;\n        msg.msgLocalID = imsg.msgLocalID;\n        msg.content = raw;\n        IMService im = IMService.getInstance();\n        im.sendGroupMessageAsync(msg);\n    }\n\n    @Override\n    protected void updateMessageContent(long id, String content) {\n        GroupMessageDB.getInstance().updateContent(id, content);\n    }\n\n    @Override\n    protected void markMessageFailure(IMessage msg) {\n        GroupMessageDB.getInstance().markMessageFailure(msg.msgLocalID);\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/outbox/Outbox.java",
    "content": "/*\n  Copyright (c) 2014-2019, GoBelieve\n    All rights reserved.\n\n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.outbox;\n\nimport android.text.TextUtils;\nimport android.webkit.MimeTypeMap;\nimport com.beetle.bauhinia.api.IMHttpAPI;\nimport com.beetle.bauhinia.api.types.Audio;\nimport com.beetle.bauhinia.api.types.Image;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Video;\nimport com.beetle.bauhinia.tools.FileCache;\nimport com.beetle.bauhinia.tools.ImageMIME;\nimport com.beetle.im.IMMessage;\n\nimport java.io.File;\nimport java.util.ArrayList;\n\nimport retrofit.mime.TypedFile;\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.functions.Action1;\n\n/**\n * Created by houxh on 16/1/18.\n */\npublic abstract class Outbox {\n\n    ArrayList<OutboxObserver> observers = new ArrayList<OutboxObserver>();\n    ArrayList<IMessage> messages = new ArrayList<IMessage>();\n\n    public void addObserver(OutboxObserver ob) {\n        if (observers.contains(ob)) {\n            return;\n        }\n        observers.add(ob);\n    }\n\n    public void removeObserver(OutboxObserver ob) {\n        observers.remove(ob);\n    }\n\n\n    public void sendMessage(IMessage imsg) {\n        boolean r = true;\n        if (imsg.content.getType() == MessageContent.MessageType.MESSAGE_AUDIO) {\n            com.beetle.bauhinia.db.message.Audio audio = (com.beetle.bauhinia.db.message.Audio) imsg.content;\n            imsg.setUploading(true);\n            if (imsg.secret) {\n                uploadSecretAudio(imsg, FileCache.getInstance().getCachedFilePath(audio.url));\n            } else {\n                uploadAudio(imsg, FileCache.getInstance().getCachedFilePath(audio.url));\n            }\n        } else if (imsg.content.getType() == MessageContent.MessageType.MESSAGE_IMAGE) {\n            com.beetle.bauhinia.db.message.Image image = (com.beetle.bauhinia.db.message.Image) imsg.content;\n            //prefix:\"file:\"\n            String path = image.url.substring(5);\n            imsg.setUploading(true);\n            if (imsg.secret) {\n                uploadSecretImage(imsg, path);\n            } else {\n                uploadImage(imsg, path);\n            }\n        } else if (imsg.content.getType() == MessageContent.MessageType.MESSAGE_VIDEO) {\n            Video video = (Video) imsg.content;\n            imsg.setUploading(true);\n            //prefix: \"file:\"\n            String path = video.thumbnail.substring(5);\n            String videoPath = FileCache.getInstance().getCachedFilePath(video.url);\n            if (imsg.secret) {\n                uploadSecretVideo(imsg, videoPath, path);\n            } else {\n                uploadVideo(imsg, videoPath, path);\n            }\n        } else if (imsg.content.getType() == MessageContent.MessageType.MESSAGE_FILE) {\n            com.beetle.bauhinia.db.message.File file = (com.beetle.bauhinia.db.message.File) imsg.content;\n            imsg.setUploading(true);\n            String filePath = FileCache.getInstance().getCachedFilePath(file.url);\n            uploadFile(imsg, filePath);\n        } else {\n            sendRawMessage(imsg, imsg.content.getRaw());\n        }\n    }\n\n\n    public boolean uploadFile(final IMessage msg, final String path) {\n        File file;\n        try {\n            file = new File(path);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n        messages.add(msg);\n        String type = null;\n        String extension = MimeTypeMap.getFileExtensionFromUrl(path);\n        if (extension != null) {\n            type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);\n        }\n        if (TextUtils.isEmpty(type)) {\n            type = \"application/octet-stream\";\n        }\n        TypedFile typedFile = new TypedFile(type, file);\n        IMHttpAPI.IMHttp imHttp = IMHttpAPI.Singleton();\n        imHttp.postFile(typedFile)\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Action1<com.beetle.bauhinia.api.types.File>() {\n                    @Override\n                    public void call(com.beetle.bauhinia.api.types.File f) {\n                        String newPath = FileCache.getInstance().getCachedFilePath(f.srcUrl);\n                        //避免重现下载\n                        new File(path).renameTo(new File(newPath));\n                        Outbox.this.saveFileURL(msg, f.srcUrl);\n                        Outbox.this.sendFileMessage(msg, f.srcUrl);\n                        onUploadFileSuccess(msg, f.srcUrl);\n                        messages.remove(msg);\n                    }\n                }, new Action1<Throwable>() {\n                    @Override\n                    public void call(Throwable throwable) {\n                        Outbox.this.markMessageFailure(msg);\n                        onUploadFileFail(msg);\n                        messages.remove(msg);\n                    }\n                });\n\n        return true;\n    }\n\n\n    public boolean uploadVideo(final IMessage msg, final String path, String thumbPath) {\n        File file;\n        try {\n            file = new File(thumbPath);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n        messages.add(msg);\n        String type = ImageMIME.getMimeType(file);\n        TypedFile typedFile = new TypedFile(type, file);\n        IMHttpAPI.IMHttp imHttp = IMHttpAPI.Singleton();\n        imHttp.postImages(type\n                , typedFile)\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Action1<Image>() {\n                    @Override\n                    public void call(Image image) {\n                        final String thumbURL = image.srcUrl;\n                        String type = \"video/mp4\";\n                        String extension = MimeTypeMap.getFileExtensionFromUrl(path);\n                        if (extension != null) {\n                            type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);\n                        }\n                        TypedFile typedFile = new TypedFile(type, new File(path));\n                        IMHttpAPI.IMHttp imHttp = IMHttpAPI.Singleton();\n                        imHttp.postFile(typedFile)\n                                .observeOn(AndroidSchedulers.mainThread())\n                                .subscribe(new Action1<com.beetle.bauhinia.api.types.File>() {\n                                    @Override\n                                    public void call(com.beetle.bauhinia.api.types.File f) {\n                                        String newPath = FileCache.getInstance().getCachedFilePath(f.srcUrl);\n                                        //避免重现下载\n                                        new File(path).renameTo(new File(newPath));\n                                        Outbox.this.saveVideoURL(msg, f.srcUrl, thumbURL);\n                                        Outbox.this.sendVideoMessage(msg, f.srcUrl, thumbURL);\n                                        onUploadVideoSuccess(msg, f.srcUrl, thumbURL);\n                                        messages.remove(msg);\n                                    }\n                                }, new Action1<Throwable>() {\n                                    @Override\n                                    public void call(Throwable throwable) {\n                                        Outbox.this.markMessageFailure(msg);\n                                        onUploadVideoFail(msg);\n                                        messages.remove(msg);\n                                    }\n                                });\n                    }\n                }, new Action1<Throwable>() {\n                    @Override\n                    public void call(Throwable throwable) {\n                        Outbox.this.markMessageFailure(msg);\n                        onUploadImageFail(msg);\n                        messages.remove(msg);\n                    }\n                });\n        return true;\n    }\n\n    public boolean uploadSecretVideo(final IMessage msg, final String path, String thumbPath) {\n        try {\n            new File(thumbPath);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n        messages.add(msg);\n        assert (msg.secret);\n        String secretFile = encryptFile(thumbPath, msg.receiver);\n        TypedFile typedFile = new TypedFile(\"\", new File(secretFile));\n        IMHttpAPI.IMHttp imHttp = IMHttpAPI.Singleton();\n        imHttp.postFile(typedFile)\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Action1<com.beetle.bauhinia.api.types.File>() {\n                    @Override\n                    public void call(com.beetle.bauhinia.api.types.File thumbnail) {\n                        final String thumbURL = thumbnail.srcUrl;\n\n                        String secretVideoFile = encryptFile(path, msg.receiver);\n                        TypedFile typedFile = new TypedFile(\"\", new File(secretVideoFile));\n                        IMHttpAPI.IMHttp imHttp = IMHttpAPI.Singleton();\n                        imHttp.postFile(typedFile)\n                                .observeOn(AndroidSchedulers.mainThread())\n                                .subscribe(new Action1<com.beetle.bauhinia.api.types.File>() {\n                                    @Override\n                                    public void call(com.beetle.bauhinia.api.types.File f) {\n\n                                        Outbox.this.sendVideoMessage(msg, f.srcUrl, thumbURL);\n                                        onUploadVideoSuccess(msg, f.srcUrl, thumbURL);\n                                        messages.remove(msg);\n\n\n                                    }\n                                }, new Action1<Throwable>() {\n                                    @Override\n                                    public void call(Throwable throwable) {\n                                        Outbox.this.markMessageFailure(msg);\n                                        onUploadVideoFail(msg);\n                                        messages.remove(msg);\n                                    }\n                                });\n                    }\n                }, new Action1<Throwable>() {\n                    @Override\n                    public void call(Throwable throwable) {\n                        Outbox.this.markMessageFailure(msg);\n                        onUploadVideoFail(msg);\n                        messages.remove(msg);\n                    }\n                });\n        return true;\n    }\n\n    public boolean uploadImage(final IMessage msg, String filePath) {\n        File file;\n        try {\n            file = new File(filePath);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n        messages.add(msg);\n        String type = ImageMIME.getMimeType(file);\n        TypedFile typedFile = new TypedFile(type, file);\n        IMHttpAPI.IMHttp imHttp = IMHttpAPI.Singleton();\n        imHttp.postImages(type\n                , typedFile)\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Action1<Image>() {\n                    @Override\n                    public void call(Image image) {\n                        Outbox.this.saveImageURL(msg, image.srcUrl);\n                        Outbox.this.sendImageMessage(msg, image.srcUrl);\n                        onUploadImageSuccess(msg, image.srcUrl);\n                        messages.remove(msg);\n                    }\n                }, new Action1<Throwable>() {\n                    @Override\n                    public void call(Throwable throwable) {\n                        Outbox.this.markMessageFailure(msg);\n                        onUploadImageFail(msg);\n                        messages.remove(msg);\n                    }\n                });\n        return true;\n    }\n\n    public boolean uploadAudio(final IMessage msg, final String filePath) {\n        messages.add(msg);\n        String type = \"audio/amr\";\n        TypedFile typedFile = new TypedFile(type, new File(filePath));\n        IMHttpAPI.IMHttp imHttp = IMHttpAPI.Singleton();\n        imHttp.postAudios(type, typedFile)\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Action1<Audio>() {\n                    @Override\n                    public void call(Audio audio) {\n                        String newPath = FileCache.getInstance().getCachedFilePath(audio.srcUrl);\n                        //避免重现下载\n                        new File(filePath).renameTo(new File(newPath));\n                        Outbox.this.saveAudioURL(msg, audio.srcUrl);\n                        Outbox.this.sendAudioMessage(msg, audio.srcUrl);\n                        onUploadAudioSuccess(msg, audio.srcUrl);\n                        messages.remove(msg);\n                    }\n                }, new Action1<Throwable>() {\n                    @Override\n                    public void call(Throwable throwable) {\n                        Outbox.this.markMessageFailure(msg);\n                        onUploadAudioFail(msg);\n                        messages.remove(msg);\n                    }\n                });\n        return true;\n    }\n\n\n    public boolean uploadSecretImage(final IMessage msg, String filePath) {\n        try {\n            new File(filePath);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n        messages.add(msg);\n        assert (msg.secret);\n        String secretFile = encryptFile(filePath, msg.receiver);\n        TypedFile typedFile = new TypedFile(\"\", new File(secretFile));\n        IMHttpAPI.IMHttp imHttp = IMHttpAPI.Singleton();\n        imHttp.postFile(typedFile)\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Action1<com.beetle.bauhinia.api.types.File>() {\n                    @Override\n                    public void call(com.beetle.bauhinia.api.types.File f) {\n                        Outbox.this.sendImageMessage(msg, f.srcUrl);\n                        onUploadImageSuccess(msg, f.srcUrl);\n                        messages.remove(msg);\n                    }\n                }, new Action1<Throwable>() {\n                    @Override\n                    public void call(Throwable throwable) {\n                        Outbox.this.markMessageFailure(msg);\n                        onUploadImageFail(msg);\n                        messages.remove(msg);\n                    }\n                });\n        return true;\n    }\n\n    public boolean uploadSecretAudio(final IMessage msg, String file) {\n        messages.add(msg);\n        assert (msg.secret);\n        String secretFile = encryptFile(file, msg.receiver);\n        TypedFile typedFile = new TypedFile(\"\", new File(secretFile));\n        IMHttpAPI.IMHttp imHttp = IMHttpAPI.Singleton();\n        imHttp.postFile(typedFile)\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Action1<com.beetle.bauhinia.api.types.File>() {\n                    @Override\n                    public void call(com.beetle.bauhinia.api.types.File f) {\n                        Outbox.this.sendAudioMessage(msg, f.srcUrl);\n                        onUploadAudioSuccess(msg, f.srcUrl);\n                        messages.remove(msg);\n                    }\n                }, new Action1<Throwable>() {\n                    @Override\n                    public void call(Throwable throwable) {\n                        Outbox.this.markMessageFailure(msg);\n                        onUploadAudioFail(msg);\n                        messages.remove(msg);\n                    }\n                });\n        return true;\n    }\n\n\n    private void onUploadAudioSuccess(IMessage msg, String url) {\n        for (OutboxObserver ob : observers) {\n            ob.onAudioUploadSuccess(msg, url);\n        }\n    }\n\n    private void onUploadAudioFail(IMessage msg) {\n        for (OutboxObserver ob : observers) {\n            ob.onAudioUploadFail(msg);\n        }\n    }\n\n    private void onUploadImageSuccess(IMessage msg, String url) {\n        for (OutboxObserver ob : observers) {\n            ob.onImageUploadSuccess(msg, url);\n        }\n    }\n\n    private void onUploadImageFail(IMessage msg) {\n        for (OutboxObserver ob : observers) {\n            ob.onImageUploadFail(msg);\n        }\n    }\n\n    private void onUploadVideoSuccess(IMessage msg, String url, String thumbURL) {\n        for (OutboxObserver ob : observers) {\n            ob.onVideoUploadSuccess(msg, url, thumbURL);\n        }\n    }\n\n    private void onUploadVideoFail(IMessage msg) {\n        for (OutboxObserver ob : observers) {\n            ob.onVideoUploadFail(msg);\n        }\n    }\n\n\n    private void onUploadFileSuccess(IMessage msg, String url) {\n        for (OutboxObserver ob : observers) {\n            ob.onFileUploadSuccess(msg, url);\n        }\n    }\n\n    private void onUploadFileFail(IMessage msg) {\n        for (OutboxObserver ob : observers) {\n            ob.onFileUploadFail(msg);\n        }\n    }\n\n    protected void sendImageMessage(IMessage imsg, String url) {\n        com.beetle.bauhinia.db.message.Image image = (com.beetle.bauhinia.db.message.Image) imsg.content;\n        com.beetle.bauhinia.db.message.Image newImage = new com.beetle.bauhinia.db.message.Image(image, url);\n        sendRawMessage(imsg, newImage.getRaw());\n    }\n\n\n    protected void sendAudioMessage(IMessage imsg, String url) {\n        com.beetle.bauhinia.db.message.Audio audio = (com.beetle.bauhinia.db.message.Audio) imsg.content;\n        com.beetle.bauhinia.db.message.Audio newAudio = new com.beetle.bauhinia.db.message.Audio(audio, url);\n        sendRawMessage(imsg, newAudio.getRaw());\n    }\n\n\n    protected void sendVideoMessage(IMessage imsg, String url, String thumbURL) {\n        Video video = (Video) imsg.content;\n        Video newVideo = new Video(video, url, thumbURL);\n        sendRawMessage(imsg, newVideo.getRaw());\n    }\n\n\n    protected void sendFileMessage(IMessage imsg, String url) {\n        com.beetle.bauhinia.db.message.File file = (com.beetle.bauhinia.db.message.File) imsg.content;\n        com.beetle.bauhinia.db.message.File newFile = com.beetle.bauhinia.db.message.File.newFile(url, file.filename, file.size);\n        newFile.generateRaw(file.getUUID(), file.getReference(), file.getGroupId());\n        sendRawMessage(imsg, newFile.getRaw());\n    }\n\n    protected void saveImageURL(IMessage msg, String url) {\n        String content = \"\";\n        if (msg.content.getType() == MessageContent.MessageType.MESSAGE_IMAGE) {\n            com.beetle.bauhinia.db.message.Image image = (com.beetle.bauhinia.db.message.Image) msg.content;\n            com.beetle.bauhinia.db.message.Image newImage = new com.beetle.bauhinia.db.message.Image(image, url);\n            content = newImage.getRaw();\n        } else {\n            return;\n        }\n\n        updateMessageContent(msg.msgLocalID, content);\n    }\n\n    protected void saveAudioURL(IMessage msg, String url) {\n        String content = \"\";\n        if (msg.content.getType() == MessageContent.MessageType.MESSAGE_AUDIO) {\n            com.beetle.bauhinia.db.message.Audio audio = (com.beetle.bauhinia.db.message.Audio) msg.content;\n            com.beetle.bauhinia.db.message.Audio newAudio = new com.beetle.bauhinia.db.message.Audio(audio, url);\n            content = newAudio.getRaw();\n        } else {\n            return;\n        }\n\n        updateMessageContent(msg.msgLocalID, content);\n    }\n\n    protected void saveVideoURL(IMessage msg, String url, String thumbURL) {\n        String content = \"\";\n        if (msg.content.getType() == MessageContent.MessageType.MESSAGE_VIDEO) {\n            Video video = (Video) msg.content;\n            Video newVideo = new Video(video, url, thumbURL);\n            content = newVideo.getRaw();\n        } else {\n            return;\n        }\n\n        updateMessageContent(msg.msgLocalID, content);\n    }\n\n\n    protected void saveFileURL(IMessage msg, String url) {\n        String content = \"\";\n        if (msg.content.getType() == MessageContent.MessageType.MESSAGE_FILE) {\n            com.beetle.bauhinia.db.message.File file = (com.beetle.bauhinia.db.message.File) msg.content;\n            com.beetle.bauhinia.db.message.File newFile = new com.beetle.bauhinia.db.message.File(file, url);\n            content = newFile.getRaw();\n        } else {\n            return;\n        }\n\n        updateMessageContent(msg.msgLocalID, content);\n    }\n\n\n    protected boolean encrypt(IMMessage msg, String uuid) {\n        assert (false);\n        return false;\n    }\n\n    protected String encryptFile(String path, long peerUID) {\n        assert (false);\n        return \"\";\n    }\n\n    abstract protected void markMessageFailure(IMessage msg);\n\n    abstract protected void updateMessageContent(long id, String content);\n\n    abstract protected void sendRawMessage(IMessage imsg, String raw);\n\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/outbox/OutboxObserver.java",
    "content": "package com.beetle.bauhinia.outbox;\n\nimport com.beetle.bauhinia.db.IMessage;\n\npublic interface OutboxObserver {\n    public void onAudioUploadSuccess(IMessage msg, String url);\n    public void onAudioUploadFail(IMessage msg);\n    public void onImageUploadSuccess(IMessage msg, String url);\n    public void onImageUploadFail(IMessage msg);\n\n    public void onVideoUploadSuccess(IMessage msg, String url, String thumbURL);\n    public void onVideoUploadFail(IMessage msg);\n\n    public void onFileUploadSuccess(IMessage msg, String url);\n    public void onFileUploadFail(IMessage msg);\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/outbox/PeerOutbox.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.outbox;\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.PeerMessageDB;\nimport com.beetle.bauhinia.db.message.*;\nimport com.beetle.im.IMMessage;\nimport com.beetle.im.IMService;\n\n\n/**\n * Created by houxh on 14-12-3.\n */\npublic class PeerOutbox extends Outbox {\n    private static final String TAG = \"goubuli\";\n\n    private static PeerOutbox instance = new PeerOutbox();\n    public static PeerOutbox getInstance() {\n        return instance;\n    }\n\n\n\n    @Override\n    protected void markMessageFailure(IMessage msg) {\n        PeerMessageDB.getInstance().markMessageFailure(msg.msgLocalID);\n    }\n\n    @Override\n    protected void updateMessageContent(long id, String content) {\n        PeerMessageDB.getInstance().updateContent(id, content);\n    }\n\n    @Override\n    protected void sendRawMessage(IMessage imsg, String raw) {\n        IMMessage msg = new IMMessage();\n        msg.sender = imsg.sender;\n        msg.receiver = imsg.receiver;\n        msg.msgLocalID = imsg.msgLocalID;\n        msg.content = raw;\n        msg.plainContent = msg.content;\n\n        boolean r = true;\n        if (imsg.secret) {\n            r = encrypt(msg, imsg.getUUID());\n        }\n\n        if (r) {\n            IMService im = IMService.getInstance();\n            im.sendPeerMessageAsync(msg);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/AudioRecorder.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.tools;\n\nimport android.content.Context;\nimport android.media.MediaPlayer;\nimport android.media.MediaPlayer.OnCompletionListener;\nimport android.media.MediaRecorder;\n\nimport android.util.Log;\n\nimport com.beetle.imkit.R;\n\npublic class AudioRecorder {\n    private static final String TAG = \"AudioRecorder\";\n\n    private Context mContext;\n    private MediaPlayer mPlayerHintStart;\n    private MediaPlayer mPlayerHintEnd;\n\n\n    public AudioRecorder(Context context, String path) {\n        this.pathName = path;\n        mContext = context;\n        mPlayerHintStart = MediaPlayer.create(context, R.raw.record_start);\n        mPlayerHintEnd = MediaPlayer.create(context, R.raw.record_end);\n    }\n\n    private boolean start = false;\n\n    private MediaRecorder recorder;\n    private String pathName;\n\n    public boolean isRecording() {\n        return this.recorder != null;\n    }\n\n    public String getPathName() {\n        return pathName;\n    }\n    public int getMaxAmplitude() {\n        return recorder.getMaxAmplitude();\n    }\n\n    public void startRecord() {\n        if (this.recorder != null) {\n            return;\n        }\n\n        try {\n            if (mPlayerHintStart != null) {\n                mPlayerHintStart.release();\n            }\n            mPlayerHintStart = MediaPlayer.create(mContext,\n                    R.raw.record_start);\n            mPlayerHintStart.start();\n            mPlayerHintStart\n                    .setOnCompletionListener(new OnCompletionListener() {\n\n                        @Override\n                        public void onCompletion(MediaPlayer arg0) {\n\n                        }\n                    });\n\n            start = true;\n            try {\n                Log.i(TAG, \"start record\");\n                AudioRecorder.this.recorder = new MediaRecorder();\n\n                recorder.setAudioSource(MediaRecorder.AudioSource.MIC);\n                recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);\n                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);\n                recorder.setOutputFile(AudioRecorder.this.pathName);\n                recorder.prepare();\n                recorder.start();\n            } catch (Exception e) {\n                Log.e(TAG,\n                        \"Record start error:  \" + e != null ? e\n                                .getMessage() : \"\");\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    public void stopRecord() {\n        if (this.recorder == null) {\n            return;\n        }\n\n        this.recorder.stop();\n        this.recorder.reset();\n        this.recorder.release();\n        this.recorder = null;\n\n        if (start) {\n            if (mPlayerHintEnd != null) {\n                mPlayerHintEnd.release();\n            }\n            mPlayerHintEnd = MediaPlayer.create(mContext, R.raw.record_end);\n            mPlayerHintEnd.start();\n        }\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/AudioUtil.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\n\npackage com.beetle.bauhinia.tools;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport android.content.Context;\nimport android.media.AudioManager;\nimport android.media.MediaPlayer;\nimport android.media.MediaPlayer.OnCompletionListener;\nimport android.util.Log;\nimport com.beetle.imkit.R;\n\n/**\n * 录音、播音的操作都是异步的，不用另起线程来执行\n * \n * @author mk\n */\npublic class AudioUtil{\n    private static final String TAG = \"AudioUtil\";\n\n    private Context mContext;\n\n    private MediaPlayer mPlayer;\n\n    private MediaPlayer mPlayerEnd;\n\n    private static MediaPlayer mDurationPlayer = new MediaPlayer();\n\n    private ArrayList<OnCompletionListener> mOnCompletionListeners = new ArrayList<OnCompletionListener>();\n\n    private ArrayList<OnStopListener> mOnStopListeners = new ArrayList<OnStopListener>();\n\n    public final static int STOP_REASON_RECORDING = 0;\n\n    public final static int STOP_REASON_OTHER = 1;\n\n    // 正在播放的文件名，多播放控制用\n    public String playingFile = \"\";\n\n    public AudioUtil(Context context) {\n        mContext = context;\n        mPlayer = new MediaPlayer();\n\n    }\n\n    /**\n     * 播放结束时调用此函数\n     * \n     * @param l\n     */\n    public void setOnCompletionListener(OnCompletionListener l) {\n        mOnCompletionListeners.add(l);\n    }\n\n    public void setOnStopListener(OnStopListener l) {\n        mOnStopListeners.add(l);\n    }\n\n    public static long getAudioDuration(String fileName) throws IOException {\n        long duration = 0;\n        if (mDurationPlayer == null) {\n            return duration;\n        }\n        try {\n            mDurationPlayer.reset();\n            mDurationPlayer.setDataSource(fileName);\n            mDurationPlayer.prepare();\n            duration = mDurationPlayer.getDuration();\n            mDurationPlayer.stop();\n        } catch (IOException e) {\n            Log.e(TAG, \"IOException:\" + e.getMessage());\n            throw e;\n        } catch (IllegalStateException e) {\n            Log.e(TAG, \"getAudioDuration start playing IllegalStateException\");\n            throw e;\n        }\n        return duration;\n    }\n\n    /**\n     * 开始播放录音\n     * \n     * @param path 录音文件的路径\n     * @return START_SUCCESS:播放成功；（TODO：文件不存在，编码不支持等）\n     * @throws IOException\n     * @throws IllegalStateException\n     */\n    public void startPlay(final String fileName) throws IllegalStateException, IOException {\n        if (fileName == null) {\n            Log.e(TAG, \"file name is null\");\n            return;\n        }\n        playingFile = fileName;\n\n        if (mPlayer == null) {\n            return;\n        }\n        if (mPlayer.isPlaying()) { // 先停止当然的播放\n            stopPlaying();\n        }\n\n        startPlaying(fileName);\n    }\n\n    /**\n     * 手动停止播音（正常情况下会自己结束）\n     */\n    public void stopPlay() throws IllegalStateException {\n        if (mPlayer != null && mPlayer.isPlaying()) {\n            stopPlaying();\n        }\n    }\n\n    /**\n     * 是否正在播放录音\n     * \n     * @return true-正在播放录音\n     */\n    public synchronized boolean isPlaying() {\n        return mPlayer != null && mPlayer.isPlaying();\n    }\n\n    /**\n     * 释放录音，播音的资源。（可以在退出单个私聊界面的时侯释放，不必每次录音结束都调用。 释放完之后，这个实例将不可再用）\n     */\n    public void release() {\n        if (mPlayer != null) {\n            mPlayer.release();\n            mPlayer = null;\n        }\n    }\n\n\n    private void startPlaying(final String fileName)\n            throws IllegalStateException, IOException {\n        if (mPlayer == null) {\n            return;\n        }\n        try {\n            mPlayer.reset();\n            mPlayer.setDataSource(fileName);\n            mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\n\n            AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            if (!am.isBluetoothA2dpOn() && !am.isWiredHeadsetOn()) {\n                am.setSpeakerphoneOn(true);\n            }\n\n            mPlayer.prepare();\n            mPlayer.start();\n\n\n            Log.i(TAG, \"start play\");\n            OnCompletionListener mOnCompletionListener = new OnCompletionListener() {\n\n                @Override\n                public void onCompletion(MediaPlayer arg0) {\n                    if (mPlayerEnd != null) {\n                        mPlayerEnd.release();\n                    }\n                    mPlayerEnd = MediaPlayer.create(mContext, R.raw.play_end);\n                    mPlayerEnd.start();\n                    for (Iterator<OnCompletionListener> itr = mOnCompletionListeners\n                            .iterator(); itr\n                            .hasNext();) {\n                        OnCompletionListener curr = itr.next();\n                        curr.onCompletion(arg0);\n                    }\n                }\n            };\n            mPlayer.setOnCompletionListener(mOnCompletionListener);\n        } catch (IOException e) {\n            Log.e(TAG, \"IOException\");\n            throw e;\n        } catch (IllegalStateException e) {\n            Log.e(TAG, \"start playing IllegalStateException\");\n            throw e;\n        }\n    }\n\n    private void stopPlaying() throws IllegalStateException {\n        stopPlaying(AudioUtil.STOP_REASON_OTHER);\n    }\n\n    private void stopPlaying(int reason) throws IllegalStateException {\n        if (mPlayer != null) {\n            try {\n                Log.i(TAG, \"_stop play\");\n                mPlayer.stop();\n                for (Iterator<OnStopListener> itr = mOnStopListeners.iterator(); itr\n                        .hasNext();) {\n                    OnStopListener curr = itr.next();\n                    curr.onStop(reason);\n                }\n            } catch (IllegalStateException e) {\n                Log.e(TAG, \"stop playing IllegalStateException\");\n                throw e;\n            }\n        }\n    }\n\n\n    public interface OnStopListener {\n        void onStop(int reason);\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/BinAscii.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.tools;\n\n/**\n * Created by houxh on 14-12-3.\n */\npublic class BinAscii {\n    private static final char HEX_DIGITS[] = {\n            '0',\n            '1',\n            '2',\n            '3',\n            '4',\n            '5',\n            '6',\n            '7',\n            '8',\n            '9',\n            'A',\n            'B',\n            'C',\n            'D',\n            'E',\n            'F'\n    };\n\n    public final static String bin2Hex(byte[] b) {\n        StringBuilder sb = new StringBuilder(b.length * 2);\n        for (int i = 0; i < b.length; i++) {\n            sb.append(HEX_DIGITS[(b[i] & 0xf0) >>> 4]);\n            sb.append(HEX_DIGITS[b[i] & 0x0f]);\n        }\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/DeviceUtil.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.tools;\n\nimport java.io.File;\n\nimport android.os.Environment;\nimport android.os.StatFs;\n\npublic class DeviceUtil {\n\tpublic static boolean isFullStorage() {\n\t\tif (!isMediaMounted())\n\t\t\treturn true;\n\n\t\tFile path = Environment.getExternalStorageDirectory();\n\t\t// 取得sdcard文件路径\n\t\tStatFs statfs = new StatFs(path.getPath());\n\t\t// 获取block的SIZE\n\t\tlong blocSize = statfs.getBlockSize();\n\t\t// 己使用的Block的数量\n\t\tlong availaBlock = statfs.getAvailableBlocks();\n\t\treturn availaBlock * blocSize < 1048576;\n\t}\n\n\tpublic static boolean isMediaMounted() {\n\t\treturn Environment.getExternalStorageState().equals(\n\t\t\t\tEnvironment.MEDIA_MOUNTED);\n\t}\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/FileCache.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.tools;\n\nimport org.apache.commons.io.IOUtils;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\n/**\n * Created by houxh on 14-12-3.\n */\npublic class FileCache {\n    public static FileCache instance = new FileCache();\n    public static FileCache getInstance() {\n        return instance;\n    }\n\n    private File dir;\n    public void setDir(File dir) {\n        this.dir = dir;\n    }\n\n\n    public void moveFile(String key, String src) throws IOException {\n        String dst = getCachedFilePath(key);\n        File srcFile = new File(src);\n        File dstFile = new File(dst);\n        boolean r = srcFile.renameTo(dstFile);\n        if (!r) {\n            //不在同一个文件系统下\n            copy(srcFile, dstFile);\n            srcFile.delete();\n        }\n    }\n\n\n    public void storeFile(String key, InputStream inputStream) throws IOException {\n        File file = new File(this.dir, getFileName(key));\n        FileOutputStream fileOutputStream = new FileOutputStream(file);\n        IOUtils.copy(inputStream, fileOutputStream);\n        fileOutputStream.flush();\n        fileOutputStream.close();\n    }\n\n    public void storeByteArray(String key, ByteArrayOutputStream byteStream) throws IOException {\n        File file = new File(this.dir, getFileName(key));\n        FileOutputStream fileOutputStream = new FileOutputStream(file);\n        byteStream.writeTo(fileOutputStream);\n        fileOutputStream.flush();\n        fileOutputStream.close();\n    }\n\n    public void storeFile(String key, byte[] data) throws IOException {\n        File file = new File(this.dir, getFileName(key));\n        FileOutputStream fileOutputStream = new FileOutputStream(file);\n        fileOutputStream.write(data);\n        fileOutputStream.flush();\n        fileOutputStream.close();\n    }\n\n\n    public void removeFile(String key) {\n        File file = new File(this.dir, getFileName(key));\n        file.delete();\n    }\n\n    public String getCachedFilePath(String key) {\n        File file = new File(this.dir, getFileName(key));\n        return file.getAbsolutePath();\n    }\n\n    public boolean isCached(String key) {\n        File file = new File(this.dir, getFileName(key));\n        return file.exists();\n    }\n\n    private String getFileName(String key) {\n        try {\n            MessageDigest md5 = MessageDigest.getInstance(\"MD5\");\n            md5.update(key.getBytes());\n            byte[] m = md5.digest();\n            String name = BinAscii.bin2Hex(m);\n            String ext = \"\";\n            int pos = -1;\n            try {\n                URL url = new URL(key);\n                pos = url.getPath().lastIndexOf(\".\");\n                if (pos != -1) {\n                    ext = url.getPath().substring(pos);\n                }\n            } catch (MalformedURLException e) {\n                e.printStackTrace();\n                pos = key.lastIndexOf(\".\");\n                if (pos != -1) {\n                    ext = key.substring(pos);\n                }\n            }\n            return name + ext;\n        } catch (NoSuchAlgorithmException e) {\n            //opps\n            System.exit(1);\n            return \"\";\n        }\n    }\n\n    public void copy(File src, File dst) throws IOException {\n        InputStream in = new FileInputStream(src);\n        try {\n            OutputStream out = new FileOutputStream(dst);\n            try {\n                // Transfer bytes from in to out\n                byte[] buf = new byte[1024];\n                int len;\n                while ((len = in.read(buf)) > 0) {\n                    out.write(buf, 0, len);\n                }\n            } finally {\n                out.close();\n            }\n        } finally {\n            in.close();\n        }\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/FileDownloader.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.tools;\nimport android.os.AsyncTask;\n\nimport android.util.Base64;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.Audio;\nimport com.beetle.bauhinia.db.message.Image;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Video;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\n\n/**\n * Created by houxh on 14-12-3.\n */\npublic class FileDownloader {\n    public interface FileDownloaderObserver {\n        public void onFileDownloadSuccess(IMessage msg);\n        public void onFileDownloadFail(IMessage msg);\n    }\n\n    private static FileDownloader instance = new FileDownloader();\n    public static FileDownloader getInstance() {\n        return instance;\n    }\n\n\n\n\n\n    ArrayList<FileDownloaderObserver> observers = new ArrayList<FileDownloaderObserver>();\n\n    ArrayList<IMessage> messages = new ArrayList<IMessage>();\n\n    public void addObserver(FileDownloaderObserver ob) {\n        if (observers.contains(ob)) {\n            return;\n        }\n        observers.add(ob);\n    }\n\n    public void removeObserver(FileDownloaderObserver ob) {\n        observers.remove(ob);\n    }\n\n    public boolean isDownloading(IMessage msg) {\n        for(IMessage m : messages) {\n            if (m.sender == msg.sender &&\n                m.receiver == msg.receiver &&\n                m.msgLocalID == msg.msgLocalID) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n\n    public void download(final IMessage imsg) {\n        if (isDownloading(imsg)) {\n            return;\n        }\n\n        String msgURL = null;\n        if (imsg.getType() == MessageContent.MessageType.MESSAGE_AUDIO) {\n            Audio audio = (Audio) imsg.content;\n            msgURL = audio.url;\n        } else if (imsg.getType() == MessageContent.MessageType.MESSAGE_IMAGE) {\n            Image image = (Image) imsg.content;\n            msgURL = image.url;\n        } else if (imsg.getType() == MessageContent.MessageType.MESSAGE_VIDEO) {\n            Video video = (Video) imsg.content;\n            msgURL = video.thumbnail;\n        } else {\n            return;\n        }\n\n        messages.add(imsg);\n        final String url = msgURL;\n\n        new AsyncTask<Void, Integer, Boolean>() {\n            @Override\n            protected Boolean doInBackground(Void... urls) {\n                try {\n                    OkHttpClient client = new OkHttpClient();\n                    Request request = new Request.Builder().url(url).build();\n                    Response response = client.newCall(request).execute();\n                    if (response.isSuccessful()) {\n                        InputStream inputStream = response.body().byteStream();\n                        FileCache.getInstance().storeFile(url, inputStream);\n                        inputStream.close();\n                        return true;\n                    } else {\n                        return false;\n                    }\n                } catch (IOException e) {\n                    return false;\n                }\n            }\n\n            @Override\n            protected void onPostExecute(Boolean result) {\n                messages.remove(imsg);\n                if (result) {\n                    FileDownloader.this.onDownloadSuccess(imsg);\n                } else {\n                    FileDownloader.this.onDownloadFail(imsg);\n                }\n            }\n        }.execute();\n    }\n\n    private void onDownloadSuccess(IMessage msg) {\n        for (FileDownloaderObserver ob : observers) {\n            ob.onFileDownloadSuccess(msg);\n        }\n    }\n\n    private void onDownloadFail(IMessage msg) {\n        for (FileDownloaderObserver ob : observers) {\n            ob.onFileDownloadFail(msg);\n        }\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/ImageMIME.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.tools;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.InputStream;\nimport java.math.BigInteger;\nimport java.util.Arrays;\n\n/**\n * Created by tsung on 11/10/14.\n */\npublic class ImageMIME {\n    public static String getMimeType(File file) {\n        try {\n            InputStream inputStream = new FileInputStream(file);\n            if (isValidPNG(inputStream)) {\n                return \"image/png\";\n            } else if (isValidJPEG(inputStream, file.length())) {\n                return \"image/jpeg\";\n            }\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n        }\n\n        return \"\";\n    }\n\n    /**\n     * Check if the image is a PNG. The first eight bytes of a PNG file always\n     * contain the following (decimal) values: 137 80 78 71 13 10 26 10 / Hex:\n     * 89 50 4e 47 0d 0a 1a 0a\n     */\n    public static boolean isValidPNG(InputStream is) {\n        try {\n            byte[] b = new byte[8];\n            is.read(b, 0, 8);\n            if (Arrays.equals(b, new BigInteger(\"89504e470d0a1a0a\", 16).toByteArray())) {\n                return false;\n            }\n        } catch (Exception e) {\n            //Ignore\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * Check if the image is a JPEG. JPEG image files begin with FF D8 and end\n     * with FF D9\n     */\n    public static boolean isValidJPEG(InputStream is, long size) {\n        try {\n            byte[] b = new byte[2];\n            is.read(b, 0, 2);\n            // check first 2 bytes:\n            if ((b[0]&0xff) != 0xff || (b[1]&0xff) != 0xd8) {\n                return false;\n            }\n            // check last 2 bytes:\n            is.skip(size-4);\n            is.read(b, 0, 2);\n            if ((b[0]&0xff) != 0xff || (b[1]&0xff) != 0xd9) {\n                return false;\n            }\n        } catch (Exception e) {\n            // Ignore\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/MapUtil.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.tools;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.net.URISyntaxException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by houxh on 2017/11/23.\n */\n\npublic class MapUtil {\n    public static boolean isAvailable(Context context, String packageName){\n        final PackageManager packageManager = context.getPackageManager();\n        //获取所有已安装程序的包信息\n        List<PackageInfo> packageInfos = packageManager.getInstalledPackages(0);\n        List<String> packageNames = new ArrayList<String>();\n        if(packageInfos != null){\n            for(int i = 0; i < packageInfos.size(); i++){\n                String packName = packageInfos.get(i).packageName;\n                packageNames.add(packName);\n            }\n        }\n        //判断packageNames中是否有目标程序的包名，有TRUE，没有FALSE\n        return packageNames.contains(packageName);\n    }\n\n    public static void openBaidu(Context context, String poiname, double longitude, double latitude) {\n\n        try {\n            poiname = (poiname != null) ? poiname : \"\";\n            Intent intent = Intent.parseUri(\"intent://map/direction?\" + \"destination=latlng:\" + latitude + \",\" + longitude + \"|name:\" + poiname, 0);\n            context.startActivity(intent);\n        } catch (URISyntaxException e) {\n            Log.e(\"intent\", e.getMessage());\n        }\n\n    }\n\n    public static boolean isBaiduAvailable(Context context) {\n        return isAvailable(context,\"com.baidu.BaiduMap\");\n    }\n    //高德地图\n    public static void openAMap(Context context, String poiname, double longitude, double latitude) {\n\n        try{\n            if (TextUtils.isEmpty(poiname)) {\n                Intent intent = Intent.parseUri(\"androidamap://navi?sourceApplication=瓜聊\" + \"&lat=\" + latitude + \"&lon=\" + longitude + \"&dev=0\", 0);\n                context.startActivity(intent);\n            } else {\n                Intent intent = Intent.parseUri(\"androidamap://navi?sourceApplication=瓜聊\" + \"&lat=\" + latitude + \"&lon=\" + longitude + \"&dev=0\"  + \"&poiname=\" + poiname, 0);\n                context.startActivity(intent);\n            }\n        } catch (URISyntaxException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static boolean isAMapAvailable(Context context) {\n        return isAvailable(context, \"com.autonavi.minimap\");\n    }\n\n    public static void openMap(Context context, String poiname, double longitude, double latitude) {\n        if (isAMapAvailable(context)) {\n            openAMap(context, poiname, longitude, latitude);\n        } else if (isBaiduAvailable(context)) {\n            openBaidu(context, poiname, longitude, latitude);\n        }\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/TimeUtil.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.tools;\n\nimport java.util.Calendar;\nimport java.util.Date;\n\n/**\n * Created by houxh on 2017/10/17.\n */\n\npublic class TimeUtil {\n\n\n    public static int now() {\n        Date date = new Date();\n        long t = date.getTime();\n        return (int)(t/1000);\n    }\n\n    public static String formatTimeBase(long ts) {\n        String s = \"\";\n        Calendar cal = Calendar.getInstance();\n        cal.setTimeInMillis((long)(ts) * 1000);\n        int year = cal.get(Calendar.YEAR);\n        int month = cal.get(Calendar.MONTH);\n        int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);\n        int hour = cal.get(Calendar.HOUR_OF_DAY);\n        int minute = cal.get(Calendar.MINUTE);\n        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);\n        String weeks[] = {\"周日\", \"周一\", \"周二\", \"周三\", \"周四\", \"周五\", \"周六\"};\n        if (isToday(ts)) {\n            s = String.format(\"%02d:%02d\", hour, minute);\n        } else if (isYesterday(ts)) {\n            s = String.format(\"昨天 %02d:%02d\", hour, minute);\n        } else if (isInWeek(ts)) {\n            s = String.format(\"%s %02d:%02d\", weeks[dayOfWeek - 1], hour, minute);\n        } else if (isInYear(ts)) {\n            s = String.format(\"%02d-%02d %02d:%02d\", month+1, dayOfMonth, hour, minute);\n        } else {\n            s = String.format(\"%d-%02d-%02d %02d:%02d\", year, month+1, dayOfMonth, hour, minute);\n        }\n        return s;\n    }\n\n    static private boolean isToday(long ts) {\n        int now = now();\n        return isSameDay(now, ts);\n    }\n\n    static private boolean isYesterday(long ts) {\n        int now = now();\n        int yesterday = now - 24*60*60;\n        return isSameDay(ts, yesterday);\n    }\n\n    static private boolean isInWeek(long ts) {\n        int now = now();\n        //6天前\n        long day6 = now - 6*24*60*60;\n        Calendar cal = Calendar.getInstance();\n        cal.setTimeInMillis(day6 * 1000);\n        cal.set(Calendar.HOUR_OF_DAY, 0);\n        cal.set(Calendar.MINUTE, 0);\n        int zero = (int)(cal.getTimeInMillis()/1000);\n        return (ts >= zero);\n    }\n\n    static private boolean isInYear(long ts) {\n        Calendar cal = Calendar.getInstance();\n        cal.setTimeInMillis(ts*1000);\n        int year = cal.get(Calendar.YEAR);\n\n        cal.setTime(new Date());\n        int y = cal.get(Calendar.YEAR);\n\n        return (year == y);\n    }\n\n    static private boolean isSameDay(long ts1, long ts2) {\n        Calendar cal = Calendar.getInstance();\n        cal.setTimeInMillis(ts1 * 1000);\n        int year1 = cal.get(Calendar.YEAR);\n        int month1 = cal.get(Calendar.MONTH);\n        int day1 = cal.get(Calendar.DAY_OF_MONTH);\n\n\n        cal.setTimeInMillis(ts2 * 1000);\n        int year2 = cal.get(Calendar.YEAR);\n        int month2 = cal.get(Calendar.MONTH);\n        int day2 = cal.get(Calendar.DAY_OF_MONTH);\n\n        return ((year1==year2) && (month1==month2) && (day1==day2));\n    }\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/tools/VideoUtil.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.tools;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.media.MediaExtractor;\nimport android.media.MediaMetadataRetriever;\nimport android.media.MediaCodecInfo;\nimport android.media.MediaFormat;\nimport android.text.TextUtils;\nimport java.io.IOException;\n\npublic class VideoUtil {\n\n    public static final String MIMETYPE_AUDIO_AAC = \"audio/mp4a-latm\";\n    public static final String MIMETYPE_VIDEO_AVC = \"video/avc\";\n\n    public static class Metadata {\n        public int width;\n        public int height;\n        public int duration;\n\n        public String videoMime;\n        public String audioMime;\n    }\n\n    public static Bitmap createVideoThumbnail(String filePath) {\n        MediaMetadataRetriever instance = new MediaMetadataRetriever();\n        try {\n            instance.setDataSource(filePath);\n            byte[] data = instance.getEmbeddedPicture();\n            if (data != null) {\n                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);\n                if (bitmap != null) return bitmap;\n            } else {\n                return instance.getFrameAtTime();\n            }\n\n            instance.release();\n        } catch (IllegalArgumentException ex) {\n            // Assume this is a corrupt video file\n            ex.printStackTrace();\n        } catch (RuntimeException ex) {\n            // Assume this is a corrupt video file.\n            ex.printStackTrace();\n        } finally {\n            instance.release();\n        }\n        return null;\n    }\n\n    public static Metadata getVideoMetadata(String filePath) {\n        Metadata size = new Metadata();\n        MediaMetadataRetriever instance = new MediaMetadataRetriever();\n        try {\n            instance.setDataSource(filePath);\n            String w = instance.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);\n            String h = instance.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);\n            String d = instance.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);\n            size.width = Integer.valueOf(w);\n            size.height = Integer.valueOf(h);\n            size.duration = Integer.valueOf(d);\n            instance.release();\n        } catch (IllegalArgumentException ex) {\n            // Assume this is a corrupt video file\n            ex.printStackTrace();\n        } catch (RuntimeException ex) {\n            // Assume this is a corrupt video file.\n            ex.printStackTrace();\n        } finally {\n            instance.release();\n        }\n\n        getTrackInfo(filePath, size);\n        return size;\n    }\n\n    private static boolean getTrackInfo(String filePath, Metadata meta) {\n        MediaExtractor instance = new MediaExtractor();\n        try {\n            instance.setDataSource(filePath);\n            int trackCount = instance.getTrackCount();\n            for (int i = 0; i < trackCount; i++) {\n                MediaFormat format = instance.getTrackFormat(i);\n                String mime = format.getString(MediaFormat.KEY_MIME);\n                if (mime.startsWith(\"video/\")) {\n                    if (TextUtils.isEmpty(meta.videoMime)) {\n                        meta.videoMime = mime;\n                    }\n                } else if (mime.startsWith(\"audio/\")) {\n                    if (TextUtils.isEmpty(meta.audioMime)) {\n                        meta.audioMime = mime;\n                    }\n                }\n            }\n            return true;\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        } finally {\n            instance.release();\n        }\n    }\n\n\n    public static boolean isAcc(String mime) {\n        return mime != null && mime.equalsIgnoreCase(MIMETYPE_AUDIO_AAC);\n    }\n    public static boolean isH264(String mime) {\n        return mime != null && mime.equalsIgnoreCase(MIMETYPE_VIDEO_AVC);\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/view/InMessageView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport com.squareup.picasso.Picasso;\nimport java.beans.PropertyChangeEvent;\nimport java.util.List;\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.imkit.R;\n\nimport co.lujun.androidtagview.TagContainerLayout;\n\npublic class InMessageView extends MessageRowView {\n    protected TextView nameView;\n\n    TagContainerLayout tagContainer;\n    boolean isShowReply;//control topicView&replyButton\n\n    public InMessageView(Context context, MessageContent.MessageType type, boolean isShowUserName) {\n        this(context, type, isShowUserName, true);\n    }\n\n    public InMessageView(Context context, MessageContent.MessageType type, boolean isShowUserName, boolean isShowReply) {\n        super(context);\n        this.isShowReply = isShowReply;\n\n        LayoutInflater inflater = LayoutInflater.from(context);\n\n        View convertView = inflater.inflate(R.layout.chat_container_left, this);\n        nameView = (TextView)convertView.findViewById(R.id.name);\n        if (isShowUserName) {\n            nameView.setVisibility(View.VISIBLE);\n        } else {\n            nameView.setVisibility(View.GONE);\n        }\n        replyButton = findViewById(R.id.reply);\n        topicView = findViewById(R.id.topic);\n\n        ViewGroup group = (ViewGroup)findViewById(R.id.content);\n        addContent(type, group);\n\n        contentFrame = findViewById(R.id.content_frame);\n        tagContainer = findViewById(R.id.tags);\n    }\n\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n        nameView.setText(msg.getSenderName());\n        updateReplyButton();\n        if (isShowReply && (!TextUtils.isEmpty(this.message.getReference()) || this.message.getReferenceCount() > 0)) {\n            topicView.setVisibility(View.VISIBLE);\n        } else {\n            topicView.setVisibility(View.GONE);\n        }\n\n        List<String> tags = msg.getTags();\n        tagContainer.setTags(tags);\n        if (tags.size() == 0) {\n            tagContainer.setVisibility(View.GONE);\n        } else {\n            tagContainer.setVisibility(View.VISIBLE);\n        }\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        if (event.getPropertyName().equals(\"senderName\")) {\n            nameView.setText(this.message.getSenderName());\n        } else if (event.getPropertyName().equals(\"senderAvatar\")) {\n            ImageView headerView = (ImageView) findViewById(R.id.header);\n            String avatar = this.message.getSenderAvatar();\n            if (headerView != null && !TextUtils.isEmpty(avatar)) {\n                Picasso.get()\n                        .load(avatar)\n                        .placeholder(R.drawable.avatar_contact)\n                        .into(headerView);\n            }\n        } else if (event.getPropertyName().equals(\"referenceCount\")) {\n            updateReplyButton();\n            if (isShowReply && (!TextUtils.isEmpty(this.message.getReference()) || this.message.getReferenceCount() > 0)) {\n                topicView.setVisibility(View.VISIBLE);\n            } else {\n                topicView.setVisibility(View.GONE);\n            }\n        } else if (event.getPropertyName().equals(\"tags\")) {\n            List<String> tags = this.message.getTags();\n            tagContainer.setTags(tags);\n            if (tags.size() == 0) {\n                tagContainer.setVisibility(View.GONE);\n            } else {\n                tagContainer.setVisibility(View.VISIBLE);\n            }\n        }\n    }\n\n    void updateReplyButton() {\n        if (this.message.getReferenceCount() > 0 && isShowReply) {\n            replyButton.setVisibility(View.VISIBLE);\n            String s = String.format(\"%d条回复\", this.message.getReferenceCount());\n            replyButton.setText(s);\n        } else {\n            replyButton.setVisibility(View.GONE);\n        }\n    }\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/view/MessageRowView.java",
    "content": "package com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.view.Gravity;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\n\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.squareup.picasso.Picasso;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.imkit.R;\n\npublic class MessageRowView extends FrameLayout implements PropertyChangeListener {\n    protected IMessage message;\n    protected MessageContentView contentView;\n    protected View contentFrame;\n    protected Button replyButton;\n    protected ImageView topicView;\n\n    public MessageRowView(Context context) {\n        super(context);\n    }\n\n    protected void addContent(MessageContent.MessageType type, ViewGroup viewGroup) {\n        Context context = getContext();\n        MessageContentView v = null;\n        switch (type) {\n            case MESSAGE_TEXT:\n                v = new MessageTextView(context);\n                break;\n            case MESSAGE_AUDIO:\n                v = new MessageAudioView(context);\n                break;\n            case MESSAGE_IMAGE:\n                v = new MessageImageView(context);\n                break;\n            case MESSAGE_LOCATION:\n                v = new MessageLocationView(context);\n                break;\n            case MESSAGE_VOIP:\n                v = new MessageVOIPView(context);\n                break;\n            case MESSAGE_LINK:\n                v = new MessageLinkView(context);\n                break;\n            case MESSAGE_GROUP_NOTIFICATION:\n            case MESSAGE_TIME_BASE:\n            case MESSAGE_HEADLINE:\n            case MESSAGE_GROUP_VOIP:\n            case MESSAGE_REVOKE:\n            case MESSAGE_ACK:\n                v = new MessageNotificationView(context);\n                break;\n            case MESSAGE_FILE:\n                v = new MessageFileView(context);\n                break;\n            case MESSAGE_VIDEO:\n                v = new MessageVideoView(context);\n                break;\n            case MESSAGE_CLASSROOM:\n                v = new MessageClassroomView(context);\n                break;\n            default:\n                v = new MessageUnknownView(context);\n                break;\n        }\n\n        if (v != null) {\n            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER_VERTICAL);\n            v.setLayoutParams(params);\n            contentView = v;\n            viewGroup.addView(v);\n        }\n    }\n\n\n    public void setMessage(IMessage msg) {\n        if (this.message != null) {\n            this.message.removePropertyChangeListener(this);\n        }\n        this.message = msg;\n        this.message.addPropertyChangeListener(this);\n\n        this.contentView.setMessage(msg);\n        this.contentView.setTag(this.message);\n        if (this.contentFrame != null) {\n            this.contentFrame.setTag(this.message);\n        }\n        if (this.replyButton != null) {\n            this.replyButton.setTag(this.message);\n        }\n\n        ImageView headerView = (ImageView)findViewById(R.id.header);\n        String avatar = msg.getSenderAvatar();\n        if (headerView != null && !TextUtils.isEmpty(avatar)) {\n            Picasso.get()\n                    .load(avatar)\n                    .placeholder(R.drawable.avatar_contact)\n                    .into(headerView);\n        }\n    }\n\n    public IMessage getMessage() {\n        return message;\n    }\n\n    public View getContentView() {\n        return contentView;\n    }\n\n    public View getContentFrame() {\n        return contentFrame;\n    }\n\n    public Button getReplyButton() {\n        return replyButton;\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n\n    }\n}"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/view/MiddleMessageView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.ViewGroup;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.imkit.R;\n\npublic class MiddleMessageView extends MessageRowView {\n\n    public MiddleMessageView(Context context, MessageContent.MessageType type) {\n        super(context);\n        LayoutInflater inflater = LayoutInflater.from(context);\n        inflater.inflate(R.layout.chat_container_center, this);\n        ViewGroup group = (ViewGroup)findViewById(R.id.content);\n        addContent(type, group);\n        contentFrame = group;\n    }\n\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n    }\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/view/OutMessageView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.ImageView;\nimport android.widget.ProgressBar;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.tools.DisplayUtils;\nimport com.beetle.imkit.R;\nimport com.squareup.picasso.Picasso;\n\nimport java.beans.PropertyChangeEvent;\nimport java.util.List;\n\nimport co.lujun.androidtagview.TagContainerLayout;\n\npublic class OutMessageView extends MessageRowView {\n\n    public Button readedButton;\n    TagContainerLayout tagContainer;\n    boolean isShowReply;\n\n    public OutMessageView(Context context, MessageContent.MessageType type, boolean isShowReply, boolean isShowReaded) {\n        super(context);\n        this.isShowReply = isShowReply;\n\n        LayoutInflater inflater = LayoutInflater.from(context);\n        inflater.inflate(R.layout.chat_container_right, this);\n        readedButton = findViewById(R.id.readed);\n        replyButton = findViewById(R.id.reply);\n        topicView = findViewById(R.id.topic);\n\n        if (isShowReaded) {\n            readedButton.setVisibility(View.VISIBLE);\n        } else {\n            readedButton.setVisibility(View.GONE);\n        }\n\n        ViewGroup group = (ViewGroup)findViewById(R.id.content);\n        addContent(type, group);\n\n        contentFrame = findViewById(R.id.content_frame);\n        tagContainer = findViewById(R.id.tags);\n    }\n\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n        readedButton.setTag(msg);\n\n        if (msg.isFailure()) {\n            ImageView flagView = (ImageView) findViewById(R.id.flag);\n            flagView.setVisibility(View.VISIBLE);\n            ProgressBar sendingProgressBar = (ProgressBar) findViewById(R.id.sending_progress_bar);\n            sendingProgressBar.setVisibility(View.GONE);\n        } else if (msg.isAck()) {\n            ImageView flagView = (ImageView) findViewById(R.id.flag);\n            flagView.setVisibility(View.GONE);\n            ProgressBar sendingProgressBar = (ProgressBar) findViewById(R.id.sending_progress_bar);\n            sendingProgressBar.setVisibility(View.GONE);\n        } else if (msg.getUploading()) {\n            ImageView flagView = (ImageView) findViewById(R.id.flag);\n            flagView.setVisibility(View.GONE);\n            ProgressBar sendingProgressBar = (ProgressBar) findViewById(R.id.sending_progress_bar);\n            sendingProgressBar.setVisibility(View.GONE);\n        } else {\n            ImageView flagView = (ImageView) findViewById(R.id.flag);\n            flagView.setVisibility(View.GONE);\n            ProgressBar sendingProgressBar = (ProgressBar) findViewById(R.id.sending_progress_bar);\n            sendingProgressBar.setVisibility(View.VISIBLE);\n        }\n        updateReadedButton();\n\n        updateReplyButton();\n        ViewGroup group = (ViewGroup)findViewById(R.id.content);\n        if (isShowReply && this.message.getReferenceCount() > 0) {\n            //保证readedButton和replyButton之间有一定的间隔\n            group.setMinimumWidth(DisplayUtils.dp2px(getContext(), 160));\n        } else if (isShowReply && !TextUtils.isEmpty(this.message.getReference())) {\n            //保证readedButton和topicView之间有一定的间隔\n            group.setMinimumWidth(DisplayUtils.dp2px(getContext(), 120));\n        } else {\n            group.setMinimumWidth(0);\n        }\n        if (isShowReply && (!TextUtils.isEmpty(this.message.getReference()) || this.message.getReferenceCount() > 0)) {\n            topicView.setVisibility(View.VISIBLE);\n        } else {\n            topicView.setVisibility(View.GONE);\n        }\n\n        List<String> tags = msg.getTags();\n        tagContainer.setTags(tags);\n        if (tags.size() == 0) {\n            tagContainer.setVisibility(View.GONE);\n        } else {\n            tagContainer.setVisibility(View.VISIBLE);\n        }\n\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        if (event.getPropertyName().equals(\"failure\") ||\n                event.getPropertyName().equals(\"ack\") ||\n                event.getPropertyName().equals(\"uploading\")||\n                event.getPropertyName().equals(\"flags\")) {\n            if (this.message.isFailure()) {\n                ImageView flagView = (ImageView) findViewById(R.id.flag);\n                flagView.setVisibility(View.VISIBLE);\n                ProgressBar sendingProgressBar = (ProgressBar) findViewById(R.id.sending_progress_bar);\n                sendingProgressBar.setVisibility(View.GONE);\n            } else if (this.message.isAck()) {\n                ImageView flagView = (ImageView) findViewById(R.id.flag);\n                flagView.setVisibility(View.GONE);\n                ProgressBar sendingProgressBar = (ProgressBar) findViewById(R.id.sending_progress_bar);\n                sendingProgressBar.setVisibility(View.GONE);\n            } else if (this.message.getUploading()) {\n                ImageView flagView = (ImageView) findViewById(R.id.flag);\n                flagView.setVisibility(View.GONE);\n                ProgressBar sendingProgressBar = (ProgressBar) findViewById(R.id.sending_progress_bar);\n                sendingProgressBar.setVisibility(View.GONE);\n            } else {\n                ImageView flagView = (ImageView) findViewById(R.id.flag);\n                flagView.setVisibility(View.GONE);\n                ProgressBar sendingProgressBar = (ProgressBar) findViewById(R.id.sending_progress_bar);\n                sendingProgressBar.setVisibility(View.VISIBLE);\n            }\n\n        } else if (event.getPropertyName().equals(\"senderAvatar\")) {\n            ImageView headerView = (ImageView) findViewById(R.id.header);\n            String avatar = this.message.getSenderAvatar();\n            if (headerView != null && !TextUtils.isEmpty(avatar)) {\n                Picasso.get()\n                        .load(avatar)\n                        .placeholder(R.drawable.avatar_contact)\n                        .into(headerView);\n            }\n        } else if (event.getPropertyName().equals(\"readed\") || event.getPropertyName().equals(\"readerCount\")) {\n            updateReadedButton();\n        } else if (event.getPropertyName().equals(\"referenceCount\")) {\n            updateReplyButton();\n            ViewGroup group = (ViewGroup)findViewById(R.id.content);\n            if (this.message.getReferenceCount() > 0) {\n                group.setMinimumWidth(DisplayUtils.dp2px(getContext(), 160));\n            } else {\n                group.setMinimumWidth(0);\n            }\n            if (isShowReply && (!TextUtils.isEmpty(this.message.getReference()) || this.message.getReferenceCount() > 0)) {\n                topicView.setVisibility(View.VISIBLE);\n            } else {\n                topicView.setVisibility(View.GONE);\n            }\n        } else if (event.getPropertyName().equals(\"tags\")) {\n            List<String> tags = this.message.getTags();\n            tagContainer.setTags(tags);\n            if (tags.size() == 0) {\n                tagContainer.setVisibility(View.GONE);\n            } else {\n                tagContainer.setVisibility(View.VISIBLE);\n            }\n        }\n    }\n\n    void updateReadedButton() {\n        if (this.message.receiverCount > 0) {\n            if (this.message.readerCount < this.message.receiverCount) {\n                int unreadCount = this.message.receiverCount - this.message.readerCount;\n                String s = String.format(getContext().getString(R.string.message_unread_count), unreadCount);\n                readedButton.setText(s);\n            } else {\n                readedButton.setText(R.string.message_readed);\n            }\n        } else if (this.message.readerCount > 0) {\n            String s = String.format(getContext().getString(R.string.message_readed_count), this.message.readerCount);\n            readedButton.setText(s);\n        } else {\n            if (this.message.isReaded()) {\n                readedButton.setText(R.string.message_readed);\n            } else {\n                readedButton.setText(R.string.message_unread);\n            }\n        }\n    }\n\n    void updateReplyButton() {\n        if (this.message.getReferenceCount() > 0) {\n            replyButton.setVisibility(View.VISIBLE);\n            String s = String.format(\"%d条回复\", this.message.getReferenceCount());\n            replyButton.setText(s);\n        } else {\n            replyButton.setVisibility(View.GONE);\n        }\n    }\n\n}\n"
  },
  {
    "path": "imkit/src/main/java/com/beetle/bauhinia/view/TagView.java",
    "content": "package com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport com.beetle.bauhinia.tools.DisplayUtils;\n\nimport co.lujun.androidtagview.TagContainerLayout;\n\npublic class TagView extends TagContainerLayout {\n    public TagView(Context context) {\n        this(context, null);\n    }\n\n    public TagView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public TagView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int w = MeasureSpec.getSize(widthMeasureSpec);\n        int maxWidth = DisplayUtils.dp2px(getContext(),240);\n        if (w > maxWidth) {\n            widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.getMode(widthMeasureSpec));\n        }\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        final int childCount = getChildCount();\n        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);\n        int availableW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();\n        int curLineW = 0;\n        int lines = childCount == 0 ? 0 : 1;\n        for (int i = 0; i < childCount; i++) {\n            View childView = getChildAt(i);\n            int dis = childView.getMeasuredWidth() + this.getHorizontalInterval();\n            curLineW += dis;\n            if (curLineW - getHorizontalInterval() > availableW) {\n                lines++;\n                curLineW = dis;\n            }\n        }\n        int width = curLineW + getPaddingLeft() + getPaddingRight();\n        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);\n        if (lines == 1 && width < availableW && widthSpecMode == MeasureSpec.AT_MOST) {\n            setMeasuredDimension(width, getMeasuredHeight());\n        } else if (maxWidth < (getMeasuredWidth() - getPaddingLeft() - getPaddingRight())) {\n            setMeasuredDimension(width, getMeasuredHeight());\n        }\n    }\n\n}\n"
  },
  {
    "path": "imkit/src/main/res/anim/fade_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n    android:duration=\"300\" />"
  },
  {
    "path": "imkit/src/main/res/anim/fade_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:fromAlpha=\"1.0\" android:toAlpha=\"0.0\"\n    android:duration=\"300\" />"
  },
  {
    "path": "imkit/src/main/res/anim/head_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 左上角扩大-->\n  <scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"0.001\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"0.001\"   \n        android:toYScale=\"1.0\"   \n        android:pivotX=\"15%\"  \n        android:pivotY=\"25%\"  \n        android:duration=\"200\" />  \n   "
  },
  {
    "path": "imkit/src/main/res/anim/head_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 左上角缩小 -->\n  <scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"0.001\"   \n        android:fromYScale=\"1.0\"   \n        android:toYScale=\"0.001\"   \n        android:pivotX=\"15%\"  \n        android:pivotY=\"25%\"  \n        android:duration=\"200\" />  \n   "
  },
  {
    "path": "imkit/src/main/res/anim/hold.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?> \n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:interpolator=\"@android:anim/accelerate_interpolator\"\n       android:fromXDelta=\"0\" android:toXDelta=\"0\"\n       android:duration=\"300\" />"
  },
  {
    "path": "imkit/src/main/res/anim/push_bottom_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"50%p\"\n        android:toYDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "imkit/src/main/res/anim/push_bottom_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    \n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"50%p\" />\n\n    \n\n</set>"
  },
  {
    "path": "imkit/src/main/res/anim/push_top_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"-50%p\"\n        android:toYDelta=\"0\" />   \n</set>"
  },
  {
    "path": "imkit/src/main/res/anim/push_top_in2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"0\"   \n        android:toYScale=\"1.0\"   \n        android:pivotX=\"0\"  \n        android:pivotY=\"10%\"  \n        android:duration=\"200\" /> "
  },
  {
    "path": "imkit/src/main/res/anim/push_top_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"-50%p\" />   \n</set>"
  },
  {
    "path": "imkit/src/main/res/anim/push_top_out2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"1.0\"   \n        android:toYScale=\"0\"   \n        android:pivotX=\"0\"  \n        android:pivotY=\"10%\"  \n        android:duration=\"200\" /> "
  },
  {
    "path": "imkit/src/main/res/anim/slide_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"-100%p\"\n        android:toXDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "imkit/src/main/res/anim/slide_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"100%p\"\n        android:toXDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "imkit/src/main/res/anim/slide_out_to_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"0\"\n        android:toXDelta=\"-100%p\" />\n\n</set>"
  },
  {
    "path": "imkit/src/main/res/anim/slide_out_to_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"0\"\n        android:toXDelta=\"100%p\" />\n\n</set>"
  },
  {
    "path": "imkit/src/main/res/drawable/chat_send_btn.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_focused=\"true\" android:state_pressed=\"true\" android:drawable=\"@drawable/voice_rcd_btn_pressed\" />\n    <item android:state_focused=\"false\" android:state_pressed=\"true\" android:drawable=\"@drawable/voice_rcd_btn_pressed\" />\n    <item android:state_focused=\"false\" android:drawable=\"@drawable/voice_rcd_btn_nor\" />\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/chatting_setmode_msg_btn.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/chatting_setmode_voice_btn_pressed\" android:state_enabled=\"true\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@drawable/chatting_setmode_voice_btn_pressed\" android:state_enabled=\"true\" android:state_selected=\"true\"/>\n    <item android:drawable=\"@drawable/chatting_setmode_voice_btn_focused\" android:state_enabled=\"true\" android:state_focused=\"true\"/>\n    <item android:drawable=\"@drawable/chatting_setmode_voice_btn_normal\" android:state_enabled=\"true\"/>\n    <item android:drawable=\"@drawable/chatting_setmode_voice_btn_pressed\" android:state_enabled=\"false\"/>\n\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/chatting_setmode_voice_btn.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/chatting_setmode_msg_btn_pressed\" android:state_enabled=\"true\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@drawable/chatting_setmode_msg_btn_pressed\" android:state_enabled=\"true\" android:state_selected=\"true\"/>\n    <item android:drawable=\"@drawable/chatting_setmode_msg_btn_focused\" android:state_enabled=\"true\" android:state_focused=\"true\"/>\n    <item android:drawable=\"@drawable/chatting_setmode_msg_btn_normal\" android:state_enabled=\"true\"/>\n    <item android:drawable=\"@drawable/chatting_setmode_msg_btn_pressed\" android:state_enabled=\"false\"/>\n\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/conversation_recording_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" \n     android:shape=\"rectangle\"> \n\t<solid android:color=\"#50000000\" />\n    <corners android:bottomRightRadius=\"10dp\" android:bottomLeftRadius=\"10dp\" \n     android:topLeftRadius=\"10dp\" android:topRightRadius=\"10dp\"/> \n</shape> "
  },
  {
    "path": "imkit/src/main/res/drawable/ease_chat_file_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item android:drawable=\"@drawable/ease_chat_file_pressed\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@drawable/ease_chat_file_pressed\" android:state_selected=\"true\"/>\n    <item android:drawable=\"@drawable/ease_chat_file_normal\"/>\n\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_chat_image_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item android:drawable=\"@drawable/ease_chat_image_pressed\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@drawable/ease_chat_image_pressed\" android:state_selected=\"true\"/>\n    <item android:drawable=\"@drawable/ease_chat_image_normal\"/>\n\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_chat_location_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item android:drawable=\"@drawable/ease_chat_location_pressed\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@drawable/ease_chat_location_normal\"/>\n\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_chat_press_speak_btn.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:state_pressed=\"true\"><shape>\n            <corners android:radius=\"3dp\" />\n\n            <padding android:bottom=\"5dp\" android:left=\"5dp\" android:right=\"5dp\" android:top=\"5dp\" />\n\n            <solid android:color=\"#d3d3d3\" />\n\n            <stroke android:width=\"0.5dp\" android:color=\"#999999\" />\n        </shape></item>\n    <item><shape>\n            <corners android:radius=\"3dp\" />\n\n            <padding android:bottom=\"5dp\" android:left=\"5dp\" android:right=\"5dp\" android:top=\"5dp\" />\n\n            <solid android:color=\"#00000000\" />\n\n            <stroke android:width=\"0.5dp\" android:color=\"#999999\" />\n        </shape></item>\n\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_chat_send_btn_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:state_pressed=\"true\"><shape android:shape=\"rectangle\">\n            <solid android:color=\"#eee\" />\n\n            <corners android:radius=\"2dp\" />\n\n            <padding android:bottom=\"2dp\" android:left=\"5dp\" android:right=\"5dp\" android:top=\"2dp\" />\n\n            <stroke android:width=\"1dp\" android:color=\"@android:color/darker_gray\" />\n        </shape></item>\n    <item><shape android:shape=\"rectangle\">\n            <solid android:color=\"@android:color/white\" />\n\n            <padding android:bottom=\"2dp\" android:left=\"5dp\" android:right=\"5dp\" android:top=\"2dp\" />\n\n            <corners android:radius=\"2dp\" />\n\n            <stroke android:width=\"1dp\" android:color=\"#cccccc\" />\n        </shape></item>\n\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_chat_takepic_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item android:drawable=\"@drawable/ease_chat_takepic_pressed\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@drawable/ease_chat_takepic_normal\"/>\n\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_chat_video_call_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/ease_chat_video_call_pressed\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@drawable/ease_chat_video_call_pressed\" android:state_selected=\"true\"/>\n    <item android:drawable=\"@drawable/ease_chat_video_call_normal\"/>\n\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_chatting_setmode_keyboard_btn.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/ease_chatting_setmode_keyboard_btn_pressed\" />\n    <item android:state_selected=\"true\" android:drawable=\"@drawable/ease_chatting_setmode_keyboard_btn_pressed\" />\n    <item android:drawable=\"@drawable/ease_chatting_setmode_keyboard_btn_normal\" />\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_chatting_setmode_voice_btn.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/ease_chatting_setmode_voice_btn_pressed\" />\n    <item android:state_selected=\"true\" android:drawable=\"@drawable/ease_chatting_setmode_voice_btn_pressed\" />\n    <item android:state_focused=\"true\" android:drawable=\"@drawable/ease_chatting_setmode_voice_btn_pressed\" />\n    <item android:drawable=\"@drawable/ease_chatting_setmode_voice_btn_normal\" />\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_edit_text_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item >\n        <shape android:shape=\"rectangle\">\n            <stroke android:width=\"2.0px\" android:color=\"@color/top_bar_normal_bg\"/>\n            <solid android:color=\"@android:color/white\"/>\n        </shape>\n    </item>\n\t<item android:bottom=\"4.0dip\">\n\t    <shape android:shape=\"rectangle\">\n\t        <solid android:color=\"@android:color/white\"/>\n\t        \n\t    </shape>\n\t    \n\t</item>\n\t \n</layer-list>\n"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_recording_text_hint_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <corners android:radius=\"5dp\" />\n\n    <solid android:color=\"#55FF0000\" />\n\n</shape>"
  },
  {
    "path": "imkit/src/main/res/drawable/ease_type_select_btn.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/ease_type_select_btn_pressed\" />\n    <item android:state_selected=\"true\" android:drawable=\"@drawable/ease_type_select_btn_pressed\" />\n    <item android:state_focused=\"true\" android:drawable=\"@drawable/ease_type_select_btn_pressed\" />\n    <item android:drawable=\"@drawable/ease_type_select_btn_nor\" />\n</selector>"
  },
  {
    "path": "imkit/src/main/res/drawable/ic_back.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\"/>\n</vector>\n"
  },
  {
    "path": "imkit/src/main/res/layout/activity_camera.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.beetle.bauhinia.activity.CameraActivity\">\n\n    <com.cjt2325.cameralibrary.JCameraView\n        android:id=\"@+id/jcameraview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:duration_max=\"10000\"\n        app:iconMargin=\"20dp\"\n        app:iconLeft=\"@drawable/ic_back\"\n        app:iconSize=\"30dp\"\n        app:iconSrc=\"@drawable/ic_camera\"\n        />\n</LinearLayout>"
  },
  {
    "path": "imkit/src/main/res/layout/activity_file.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        tools:context=\"com.beetle.bauhinia.activity.MessageFileActivity\">\n\n\n    <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:srcCompat=\"@drawable/word\"\n            android:id=\"@+id/imageView\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            android:layout_marginTop=\"44dp\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            android:layout_marginStart=\"8dp\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            android:layout_marginEnd=\"8dp\"/>\n\n    <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"39dp\"\n            android:inputType=\"textPersonName\"\n            android:text=\"test.docx\"\n            android:ems=\"10\"\n            android:id=\"@+id/filename\"\n            android:layout_marginTop=\"20dp\"\n            app:layout_constraintTop_toBottomOf=\"@+id/imageView\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            android:layout_marginStart=\"8dp\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            android:layout_marginEnd=\"8dp\"\n            android:gravity=\"center\"/>\n\n    <Button\n            android:text=\"用其他应用打开\"\n            android:layout_width=\"210dp\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/open\"\n            android:background=\"@color/btn_green_noraml\"\n            android:onClick=\"onOpen\"\n\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            android:layout_marginEnd=\"8dp\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            android:layout_marginStart=\"8dp\"\n\n            app:layout_constraintTop_toTopOf=\"parent\"\n            android:layout_marginTop=\"8dp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            android:layout_marginBottom=\"8dp\"/>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "imkit/src/main/res/layout/activity_overlay.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/white\"\n        tools:context=\"com.beetle.bauhinia.activity.OverlayActivity\">\n\n    <TextView\n            android:id=\"@+id/text\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center\"\n            android:minHeight=\"38dp\"\n            android:lineSpacingExtra=\"2dp\"\n            android:textColor=\"#000000\"\n            android:textSize=\"24sp\"\n            android:paddingLeft=\"12dp\"\n            android:paddingRight=\"12dp\"\n            android:text=\"adsfkasdkfljaksldfjkl\\naksdfkasdfjkals;dfa;slkdjfkljasdfkjlaskljdfljkasdfkljasdfljksdajfkklj;sdF考虑；将阿瑟费的空间；老大双方立即开始打开链接；啊三等奖；卡；开始了减肥的；kljasfdkljklsajfdkljasdfkjlasdfkjl\"\n         />\n</FrameLayout>\n"
  },
  {
    "path": "imkit/src/main/res/layout/activity_photo.xml",
    "content": "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    tools:context=\"com.beetle.bauhinia.activity.PhotoActivity\">\n\n    <ImageView\n        android:id=\"@+id/photo\"\n        android:layout_gravity=\"center\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerInside\"\n        />\n\n</FrameLayout>\n"
  },
  {
    "path": "imkit/src/main/res/layout/activity_player.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        tools:context=\"com.beetle.bauhinia.activity.PlayerActivity\">\n     <VideoView\n             android:id=\"@+id/video\"\n             android:layout_width=\"match_parent\"\n             android:layout_height=\"match_parent\" tools:layout_editor_absoluteY=\"0dp\"\n             tools:layout_editor_absoluteX=\"0dp\"/>\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "imkit/src/main/res/layout/activity_web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <WebView\n        android:id=\"@+id/webview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        style=\"?android:attr/progressBarStyleHorizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"0dp\"/>\n</FrameLayout>"
  },
  {
    "path": "imkit/src/main/res/layout/chat.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:focusable=\"true\"\n        android:focusableInTouchMode=\"true\"\n        android:layout_width=\"0px\"\n        android:layout_height=\"0px\"/>\n\n    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n        android:id=\"@+id/swipe_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_weight=\"1\">\n\n        <ListView\n            android:id=\"@+id/list_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:divider=\"@null\"\n            android:dividerHeight=\"5dp\"\n            android:scrollbarStyle=\"outsideOverlay\"\n            android:stackFromBottom=\"false\"\n            android:layout_weight=\"1\"\n            android:transcriptMode=\"normal\"\n            android:cacheColorHint=\"#00000000\"\n            android:listSelector=\"#00000000\"\n            />\n\n    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n    <com.beetle.bauhinia.toolbar.EaseChatInputMenu\n        android:id=\"@+id/input_menu\"\n        android:layout_weight=\"0\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"/>\n\n    <LinearLayout android:id=\"@+id/bottom_bar\"\n                  android:layout_width=\"match_parent\"\n                  android:layout_height=\"wrap_content\"\n                  android:layout_weight=\"0\"\n                  android:orientation=\"vertical\"\n                  android:visibility=\"gone\">\n\n        <View android:layout_width=\"match_parent\"\n              android:layout_height=\"0.5dp\"\n              android:background=\"@color/gray_normal\" />\n\n        <TextView\n                android:id=\"@+id/bottom_text\"\n                android:layout_width=\"match_parent\"\n                  android:layout_height=\"44dp\"\n                  android:text=\"无法输入\"\n                  android:gravity=\"center\"/>\n    </LinearLayout>\n\n\n\n\n\n</LinearLayout>\n\n"
  },
  {
    "path": "imkit/src/main/res/layout/chat_container_center.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"5dp\"\n    android:layout_marginRight=\"5dp\"\n    android:gravity=\"center\"\n    android:focusable=\"false\"\n    android:descendantFocusability=\"blocksDescendants\"\n    >\n\n    <FrameLayout\n        android:id=\"@+id/content\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"10dp\"\n        >\n\n    </FrameLayout>\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "imkit/src/main/res/layout/chat_container_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginLeft=\"5dp\"\n    android:layout_marginTop=\"5dp\"\n    android:focusable=\"false\"\n    android:descendantFocusability=\"blocksDescendants\">\n\n    <ImageView\n        android:id=\"@+id/header\"\n        android:layout_width=\"48dp\"\n        android:layout_height=\"48dp\"\n        android:layout_marginLeft=\"4dp\"\n        android:src=\"@drawable/avatar_contact\"/>\n\n    <TextView\n        android:id=\"@+id/name\"\n        android:text=\"名称\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_toRightOf=\"@+id/header\"\n        android:layout_marginBottom=\"4dp\"\n        android:layout_marginLeft=\"10dp\"/>\n\n    <LinearLayout\n        android:id=\"@+id/content_frame\"\n        android:orientation=\"vertical\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_toRightOf=\"@+id/header\"\n        android:layout_below=\"@+id/name\"\n        android:layout_marginRight=\"20dp\"\n        android:background=\"@drawable/chatfrom_bg\">\n        <FrameLayout\n            android:id=\"@+id/content\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"/>\n\n        <com.beetle.bauhinia.view.TagView\n            android:id=\"@+id/tags\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"4dp\"\n\n            app:container_border_width=\"0dp\"\n            app:container_background_color=\"#00000000\"\n            app:container_border_color=\"@color/transparent\"\n            app:container_border_radius=\"0dp\"\n            app:container_enable_drag=\"false\"\n\n            app:horizontal_interval=\"4dp\"\n            app:vertical_interval=\"4dp\"\n\n            app:tag_background_color=\"@color/tag_background\"\n            app:tag_border_color=\"@color/transparent\"\n\n            app:tag_text_color=\"@color/tag_text\"\n            app:tag_text_size=\"14sp\"\n            app:tag_clickable=\"true\"\n            app:tag_corner_radius=\"4dp\"\n            app:tag_theme=\"none\" />\n    </LinearLayout>\n\n\n    <ImageView\n        android:id=\"@+id/topic\"\n        android:layout_width=\"16dp\"\n        android:layout_height=\"16dp\"\n        android:src=\"@drawable/topic\"\n        android:layout_below=\"@+id/content_frame\"\n        android:layout_marginTop=\"2dp\"\n        android:layout_marginLeft=\"14dp\"\n        android:layout_alignLeft=\"@+id/content_frame\"/>\n\n    <Button\n        android:id=\"@+id/reply\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"0dp\"\n        android:minWidth=\"0dp\"\n        android:paddingTop=\"2dp\"\n        android:paddingBottom=\"2dp\"\n        android:text=\"0条回复\"\n        android:textSize=\"12sp\"\n        android:textColor=\"@color/btn_readed\"\n        android:background=\"@color/transparent\"\n        android:layout_below=\"@+id/content_frame\"\n        android:layout_toRightOf=\"@+id/topic\"/>\n\n</RelativeLayout>\n"
  },
  {
    "path": "imkit/src/main/res/layout/chat_container_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"5dp\"\n    android:layout_marginRight=\"5dp\"\n    android:gravity=\"right\"\n    android:focusable=\"false\"\n    android:descendantFocusability=\"blocksDescendants\">\n\n    <LinearLayout\n        android:id=\"@+id/content_frame\"\n        android:orientation=\"vertical\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/chatto_bg\">\n        <FrameLayout\n            android:id=\"@+id/content\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            >\n        </FrameLayout>\n\n        <com.beetle.bauhinia.view.TagView\n            android:id=\"@+id/tags\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"4dp\"\n\n            app:container_border_width=\"0dp\"\n            app:container_background_color=\"#00000000\"\n            app:container_border_color=\"@color/transparent\"\n            app:container_border_radius=\"0dp\"\n\n            app:horizontal_interval=\"4dp\"\n            app:vertical_interval=\"4dp\"\n\n            app:tag_background_color=\"@color/tag_background\"\n            app:tag_border_color=\"@color/transparent\"\n\n            app:tag_text_color=\"@color/tag_text\"\n            app:tag_text_size=\"14sp\"\n            app:tag_clickable=\"true\"\n            app:tag_corner_radius=\"4dp\"\n            app:tag_theme=\"none\" />\n\n    </LinearLayout>\n\n\n    <Button\n        android:id=\"@+id/readed\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"0dp\"\n        android:minWidth=\"0dp\"\n        android:paddingTop=\"2dp\"\n        android:paddingBottom=\"2dp\"\n        android:text=\"未读\"\n        android:textSize=\"12sp\"\n        android:textColor=\"@color/btn_readed\"\n        android:background=\"@color/transparent\"\n        android:layout_below=\"@+id/content_frame\"\n        android:layout_alignRight=\"@+id/content_frame\"\n        android:layout_marginRight=\"20dp\" />\n\n    <ImageView\n        android:id=\"@+id/topic\"\n        android:layout_width=\"16dp\"\n        android:layout_height=\"16dp\"\n        android:src=\"@drawable/topic\"\n        android:layout_below=\"@+id/content_frame\"\n        android:layout_marginTop=\"2dp\"\n        android:layout_marginLeft=\"4dp\"\n        android:layout_alignLeft=\"@+id/content_frame\"/>\n\n    <Button\n        android:id=\"@+id/reply\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"0dp\"\n        android:minWidth=\"0dp\"\n        android:text=\"0条回复\"\n        android:textSize=\"12sp\"\n        android:textColor=\"@color/btn_readed\"\n        android:background=\"@color/transparent\"\n        android:layout_below=\"@+id/content_frame\"\n        android:layout_toRightOf=\"@+id/topic\"/>\n\n    <ImageView\n        android:id=\"@+id/header\"\n        android:layout_width=\"48dp\"\n        android:layout_height=\"48dp\"\n        android:layout_marginRight=\"4dp\"\n        android:layout_toRightOf=\"@+id/content_frame\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@drawable/avatar_contact\"/>\n\n    <ImageView\n        android:id=\"@+id/flag\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_toLeftOf=\"@+id/content_frame\"\n        android:layout_alignBottom=\"@+id/content_frame\"\n        android:layout_marginBottom=\"4dp\"\n        android:layout_marginRight=\"4dp\"\n        android:src=\"@drawable/msg_status_send_error\" />\n\n    <ProgressBar\n        android:id=\"@+id/sending_progress_bar\"\n        android:layout_width=\"25dp\"\n        android:layout_height=\"25dp\"\n        android:layout_toLeftOf=\"@+id/content_frame\"\n        android:layout_alignBottom=\"@+id/content_frame\"\n        android:layout_marginBottom=\"4dp\"\n        android:layout_marginRight=\"4dp\"\n        android:visibility=\"visible\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "imkit/src/main/res/layout/chat_location.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n\n\n    <com.amap.api.maps2d.MapView\n        android:id=\"@+id/map\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/map_address\"\n        android:padding=\"12dp\"\n        android:layout_gravity=\"bottom\">\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginRight=\"64dp\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:id=\"@+id/township\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"/>\n\n            <TextView\n                android:id=\"@+id/address\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"/>\n        </LinearLayout>\n\n        <ImageButton\n            android:id=\"@+id/button\"\n            android:layout_width=\"64dp\"\n            android:layout_height=\"64dp\"\n            android:layout_gravity=\"right\"\n            android:scaleType=\"fitCenter\"\n            android:onClick=\"onNavigation\"\n            app:srcCompat=\"@drawable/navigation\" />\n    </FrameLayout>\n\n</FrameLayout>"
  },
  {
    "path": "imkit/src/main/res/layout/conversation_recording_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n\tandroid:orientation=\"vertical\"\n    android:background=\"@drawable/conversation_recording_round\"\n\tandroid:id=\"@+id/conversation_recording\"\n    android:gravity=\"center\"\n\tandroid:padding=\"50dp\">\n\t<FrameLayout\n        android:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<ImageView\n            android:id=\"@+id/conversation_recording_white\"\n\t\t\tandroid:src=\"@drawable/record_white\"\n            android:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\" />\n\n\t\t<ImageView android:id=\"@+id/conversation_recording_range\"\n\t\t\tandroid:src=\"@drawable/record_red\"\n            android:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n            android:layout_gravity=\"bottom\"\n\t\t\tandroid:layout_marginBottom=\"0px\"\n            android:cropToPadding=\"true\"\n\t\t\tandroid:adjustViewBounds=\"false\"\n            android:scrollY=\"0px\"/>\n\n\t</FrameLayout>\n\n\t<TextView\n        android:layout_marginTop=\"38dp\"\n\t\tandroid:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n\t\tandroid:text=\"@string/move_up_to_cancel\"\n        android:id=\"@+id/conversation_recording_text\"\n\t\tandroid:textColor=\"@color/white\"\n        android:textSize=\"14sp\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "imkit/src/main/res/layout/location_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:animateLayoutChanges=\"true\">\n\n    <com.amap.api.maps2d.MapView\n        android:id=\"@+id/map\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:src=\"@drawable/ic_pin_hole\" />\n\n    <ImageView\n        android:id=\"@+id/pin\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_marginBottom=\"@dimen/pin_margin\"\n        android:src=\"@drawable/ic_pin\" />\n\n    <TextView\n        android:id=\"@+id/label\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_marginBottom=\"62dp\"\n        android:background=\"@drawable/bg_location\"\n        android:visibility=\"gone\" />\n</FrameLayout>"
  },
  {
    "path": "imkit/src/main/res/menu/chat.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    >\n\n    <item android:id=\"@+id/action_clear\"\n        android:title=\"清除对话\"\n        app:showAsAction=\"never\"\n        />\n\n</menu>"
  },
  {
    "path": "imkit/src/main/res/menu/location_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    >\n    <item android:id=\"@+id/action_send\"\n        android:title=\"发送\"\n        app:showAsAction=\"always\"\n        />\n</menu>"
  },
  {
    "path": "imkit/src/main/res/menu/menu_photo.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:context=\"com.beetle.bauhinia.activity.PhotoActivity\">\n</menu>\n"
  },
  {
    "path": "imkit/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"map_address\">#fafafa</color>\n    <color name=\"gray_normal\">#666667</color>\n    <color name=\"btn_green_noraml\">#68C270</color>\n    <color name=\"top_bar_normal_bg\">#00acff</color>\n    <color name=\"btn_readed\">#4d98f6</color>\n    <color name=\"transparent\">#00000000</color>\n    <color name=\"lightgray\">#d3d3d3</color>\n    <color name=\"tag_background\">#c9d8d5</color>\n    <color name=\"tag_text\">#787878</color>\n</resources>"
  },
  {
    "path": "imkit/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"pin_margin\">19dp</dimen>\n</resources>"
  },
  {
    "path": "imkit/src/main/res/values/strings.xml",
    "content": "<resources>\n\n    <string name=\"product_fotos_get_from\">Get photo from:</string>\n\n    <string name=\"im_title_choose\">Choose</string>\n    <string name=\"im_btn_forward\">Forward</string>\n    <string name=\"im_btn_resend\">Resend</string>\n    <string name=\"im_btn_copy\">Copy</string>\n    <string name=\"im_btn_del\">Delete</string>\n    <string name=\"im_btn_revoke\">Recall</string>\n    <string name=\"im_btn_reply\">Reply</string>\n    <string name=\"im_btn_tag\">Tag</string>\n\n    <string name=\"button_pushtotalk\">Hold to talk</string>\n    <string name=\"button_send\">Send</string>\n\n    <string name=\"move_up_to_cancel\">Finger up to cancel sending</string>\n    <string name=\"release_to_cancel\">Finger release to cancel sending</string>\n\n\n    <string name=\"attach_picture\">Image</string>\n    <string name=\"attach_take_pic\">Capture</string>\n    <string name=\"attach_location\">Location</string>\n    <string name=\"attach_voice_call\">Voice call</string>\n    <string name=\"attach_video_call\">Video call</string>\n    <string name=\"attach_file\">File</string>\n\n    <string name=\"gallery_image_saved_to\">Image saved to </string>\n    <string name=\"gallery_image_save_failed\">Image save failed!</string>\n    <string name=\"gallery_save_to_phone\">Save to phone</string>\n    <string name=\"gallery_chat_files\">Chat Files</string>\n\n    <string name=\"not_enough_storage_space\">Not enough storage space.</string>\n\n\n    <string name=\"message_create_group\">You created \\\"%s\\\" group</string>\n    <string name=\"message_me_join_group\">You joined \\\"%s\\\" group</string>\n    <string name=\"message_join_group\">\\\"%s\\\" joined group</string>\n    <string name=\"message_leave_group\">\\\"%s\\\" leaved group</string>\n    <string name=\"message_change_group_name\">Group name changed:\\\"%s\\\"</string>\n    <string name=\"message_group_notice\">Group notice:%s</string>\n    <string name=\"message_group_disbanded\">Group disbanded</string>\n\n    <string name=\"message_group_call_start\">%s started a Walkie Talkie session</string>\n    <string name=\"message_group_call_finished\">Walkie Talkie session ended</string>\n\n    <string name=\"message_revoked\">%s revoked a message</string>\n\n    <string name=\"you\">You</string>\n\n\n    <string name=\"message_not_friend\">You are not his/her friend</string>\n    <string name=\"message_refuesed\">Message sent, but be refused.</string>\n    <string name=\"message_not_my_friend\">The other user is not your friend</string>\n\n    <string name=\"video_record_duration_warning\">Video recording time is too short</string>\n    <string name=\"unsupported_video_encoding_warning\">This video codec is not supported.</string>\n\n    <string name=\"voice_record_duration_warning\">Audio recording time is too short</string>\n    <string name=\"file_size_limit_warning\">File size limit:%dM</string>\n    <string name=\"revoke_timed_out\">More than two minutes, the message revoke failed</string>\n\n    <string name=\"revoke_connection_disconnect\">Service disconnected, the message revoke failed</string>\n\n    <string name=\"revoke_failure\">Revoke failed!</string>\n\n    <string name=\"conversation_encrypt_flag\">(encrypt)</string>\n\n\n    <string name=\"message_readed\">readed</string>\n    <string name=\"message_unread\">unread</string>\n    <string name=\"message_unread_count\">%d unread</string>\n    <string name=\"message_readed_count\">%d readed</string>\n</resources>\n"
  },
  {
    "path": "imkit/src/main/res/values/styles.xml",
    "content": "<resources>\n    <style name=\"imkit.ActionBar\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <item name=\"android:windowActionBar\">true</item>\n    </style>\n\n    <style name=\"imkit.NoActionBar\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n        <item name=\"android:windowActionBar\">false</item>\n        <item name=\"android:windowNoTitle\">true</item>\n    </style>\n\n    <style name=\"chat_text_date_style\">\n        <item name=\"android:padding\">2dp</item>\n        <item name=\"android:textColor\">#ffffff</item>\n        <item name=\"android:textSize\">12sp</item>\n        <item name=\"android:background\">#bfbfbf</item>\n    </style>\n\n    <style name=\"chat_content_date_style\">\n        <item name=\"android:minHeight\">50dp</item>\n        <item name=\"android:gravity\">center</item>\n        <item name=\"android:textColor\">#000000</item>\n        <item name=\"android:textSize\">15sp</item>\n        <item name=\"android:lineSpacingExtra\">2dp</item>\n        <item name=\"android:background\">#bfbfbf</item>\n    </style>\n\n\n    <style name=\"chat_text_name_style\">\n        <item name=\"android:textColor\">#818181</item>\n        <item name=\"android:textSize\">15sp</item>\n        <item name=\"android:gravity\">center</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "imkit/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"product_fotos_get_from\">Get photo from:</string>\n\n    <string name=\"im_title_choose\">选择操作</string>\n    <string name=\"im_btn_forward\">转发</string>\n    <string name=\"im_btn_resend\">重发</string>\n    <string name=\"im_btn_copy\">复制</string>\n    <string name=\"im_btn_del\">删除</string>\n    <string name=\"im_btn_revoke\">撤回</string>\n    <string name=\"im_btn_reply\">回复</string>\n    <string name=\"im_btn_tag\">标签</string>\n\n    <string name=\"button_pushtotalk\">按住说话</string>\n    <string name=\"button_send\">发送</string>\n\n    <string name=\"move_up_to_cancel\">手指上滑，取消发送</string>\n    <string name=\"release_to_cancel\">松开手指，取消发送</string>\n\n    <string name=\"attach_picture\">图片</string>\n    <string name=\"attach_take_pic\">拍摄</string>\n    <string name=\"attach_location\">位置</string>\n    <string name=\"attach_voice_call\">语音电话</string>\n    <string name=\"attach_video_call\">视频通话</string>\n    <string name=\"attach_file\">文件</string>\n\n    <string name=\"gallery_image_saved_to\">图片已保存至</string>\n    <string name=\"gallery_save_to_phone\">保存至手机</string>\n    <string name=\"gallery_image_save_failed\">图片保存失败</string>\n    <string name=\"gallery_chat_files\">聊天文件</string>\n    <string name=\"not_enough_storage_space\">存储空间不足</string>\n\n    <string name=\"message_create_group\">你创建了\\\"%s\\\"群组</string>\n    <string name=\"message_me_join_group\">你加入了\"%s\"群组</string>\n    <string name=\"message_join_group\">\"%s\"加入群</string>\n    <string name=\"message_leave_group\">\"%s\"离开群</string>\n    <string name=\"message_change_group_name\">群组改名为\\\"%s\\\"</string>\n    <string name=\"message_group_notice\">群公告:%s</string>\n    <string name=\"message_group_disbanded\">群组已解散</string>\n\n    <string name=\"message_group_call_start\">%s发起了语音聊天</string>\n    <string name=\"message_group_call_finished\">语音聊天已结束</string>\n\n    <string name=\"message_revoked\">%s撤回了一条消息</string>\n\n    <string name=\"you\">你</string>\n\n\n    <string name=\"message_not_friend\">你还不是他（她）朋友</string>\n    <string name=\"message_refuesed\">消息已发出，但被对方拒收了。</string>\n    <string name=\"message_not_my_friend\">对方已不是你的朋友</string>\n\n\n    <string name=\"video_record_duration_warning\">拍摄时间太短了</string>\n    <string name=\"unsupported_video_encoding_warning\">不支持的视频编码</string>\n\n    <string name=\"voice_record_duration_warning\">录音时间太短了</string>\n\n    <string name=\"file_size_limit_warning\">文件大小不能超过%dM</string>\n\n    <string name=\"revoke_timed_out\">超过两分钟，消息撤回失败</string>\n\n    <string name=\"revoke_connection_disconnect\">网络连接断开，撤回失败</string>\n\n    <string name=\"revoke_failure\">撤回失败</string>\n\n    <string name=\"conversation_encrypt_flag\">(密)</string>\n\n    <string name=\"message_readed\">已读</string>\n    <string name=\"message_unread\">未读</string>\n    <string name=\"message_unread_count\">%d人未读</string>\n    <string name=\"message_readed_count\">%d人已读</string>\n</resources>"
  },
  {
    "path": "imkit/src/main/res/xml/file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <external-cache-path name=\"external\" path=\"/\" />\n    <external-files-path name=\"download\" path=\"download/\"/>\n    <files-path name=\"cache\" path=\"cache/\"/>\n</paths>"
  },
  {
    "path": "imlib/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n    buildToolsVersion rootProject.ext.buildToolsVersion\n\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_7\n        targetCompatibility JavaVersion.VERSION_1_7\n    }\n\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n\n    implementation project(':imsdk')\n\n    implementation 'androidx.appcompat:appcompat:1.1.0'\n    implementation 'androidx.recyclerview:recyclerview:1.0.0';\n\n    implementation 'com.google.code.gson:gson:2.8.6'\n    implementation 'com.squareup.picasso:picasso:2.71828'\n    implementation 'com.squareup.retrofit:retrofit:1.9.0'\n    implementation 'com.netflix.rxjava:rxjava-core:0.20.7'\n    implementation 'com.netflix.rxjava:rxjava-android:0.20.7'\n\n    implementation 'org.apache.commons:commons-io:1.3.2'\n    implementation 'joda-time:joda-time:2.10.5'\n    implementation 'com.commit451:PhotoView:1.2.5'\n\n    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'\n    api 'com.linkedin.android.spyglass:spyglass:2.0.1'\n\n}\n"
  },
  {
    "path": "imlib/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.beetle.imlib\" />\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/activity/BaseActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.activity;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.os.Bundle;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.util.Log;\nimport android.view.MenuItem;\nimport com.beetle.im.IMService;\n\nimport java.util.List;\n\n/**\n * Created by tsung on 12/10/14.\n */\npublic class BaseActivity extends AppCompatActivity {\n    protected ActionBar actionBar;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        IMService.getInstance().enterForeground();\n\n        actionBar = getSupportActionBar();\n        if (actionBar != null) {\n            if (canBack()) {\n                actionBar.setDisplayHomeAsUpEnabled(true);\n            } else {\n                actionBar.setDisplayHomeAsUpEnabled(false);\n            }\n            actionBar.show();\n        }\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n\n        if (!isAppOnForeground()) {\n            //app 进入后台,停止IMService,采用push机制接收离线消息\n            Log.i(\"im\", \"app enter background\");\n            IMService.getInstance().enterBackground();\n        }\n    }\n\n    public boolean canBack() {\n        return true;\n    }\n\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                if (canBack()) {\n                    onBackPressed();\n                    return true;\n                }\n        }\n        return false;\n    }\n\n\n    /**\n     * 程序是否在前台运行\n     *\n     * @return\n     */\n    public boolean isAppOnForeground() {\n        // Returns a list of application processes that are running on the\n        // device\n\n        ActivityManager activityManager =\n                (ActivityManager) getApplicationContext().getSystemService(\n                        Context.ACTIVITY_SERVICE);\n        String packageName = getApplicationContext().getPackageName();\n\n        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager\n                .getRunningAppProcesses();\n        if (appProcesses == null)\n            return false;\n\n        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {\n            // The name of the process that this object is associated with.\n            if (appProcess.processName.equals(packageName)\n                    && appProcess.importance\n                    == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    protected void showBack(boolean show) {\n        actionBar = getSupportActionBar();\n        if (actionBar != null) {\n            actionBar.setDisplayHomeAsUpEnabled(show);\n            actionBar.show();\n        }\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/ConversationIterator.java",
    "content": "package com.beetle.bauhinia.db;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\n\n/**\n * Created by houxh on 15/3/9.\n */\npublic interface ConversationIterator {\n    public IMessage next();\n}\n\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/CustomerMessageDB.java",
    "content": "package com.beetle.bauhinia.db;\n\nimport com.beetle.bauhinia.db.message.Location;\n\n/**\n * Created by houxh on 16/1/18.\n */\npublic class CustomerMessageDB extends SQLCustomerMessageDB implements IMessageDB {\n    private static CustomerMessageDB instance = new CustomerMessageDB();\n\n    public static CustomerMessageDB getInstance() {\n        return instance;\n    }\n\n\n\n    public boolean clearConversation(String conversationID) {\n        long storeId = Long.parseLong(conversationID);\n        return clearConversation(storeId);\n    }\n\n    public void saveMessageAttachment(IMessage msg, String address) {\n        Location loc = (Location)msg.content;\n        loc = Location.newLocation(loc.latitude, loc.longitude, address);\n        this.updateContent(msg.msgLocalID, loc.getRaw());\n    }\n\n    public void saveMessage(IMessage imsg) {\n        assert(imsg.isOutgoing);\n        ICustomerMessage m = (ICustomerMessage)imsg;\n        this.insertMessage(imsg, m.receiverAppID, m.receiver);\n    }\n\n    public void removeMessage(IMessage imsg) {\n        this.removeMessage(imsg.msgLocalID);\n    }\n\n\n    public void markMessageListened(IMessage imsg) {\n        this.markMessageListened(imsg.msgLocalID);\n    }\n\n\n    public void markMessageFailure(IMessage imsg) {\n        this.markMessageFailure(imsg.msgLocalID);\n    }\n\n\n    public void eraseMessageFailure(IMessage imsg) {\n        this.eraseMessageFailure(imsg.msgLocalID);\n    }\n\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/CustomerPeerMessageDB.java",
    "content": "package com.beetle.bauhinia.db;\n\nimport com.beetle.bauhinia.db.message.Location;\n\n/**\n * Created by houxh on 16/1/18.\n */\npublic class CustomerPeerMessageDB extends SQLCustomerMessageDB implements IMessageDB {\n    private static CustomerPeerMessageDB instance = new CustomerPeerMessageDB();\n\n    public static CustomerPeerMessageDB getInstance() {\n        return instance;\n    }\n\n\n    private static class PeerId {\n        public long appid;\n        public long uid;\n    }\n\n    PeerId parsePeerId(String conversationID) {\n        PeerId id = new PeerId();\n        int p = conversationID.indexOf('_');\n        if (p != -1) {\n            String p1 = conversationID.substring(0, p);\n            String p2= conversationID.substring(p + 1);\n            id.appid = Long.parseLong(p1);\n            id.uid = Long.parseLong(p2);\n        }\n        return id;\n\n    }\n    //获取最近的消息\n    public MessageIterator newMessageIterator(String conversationID) {\n        PeerId id = parsePeerId(conversationID);\n        return newCustomerPeerMessageIterator(id.appid, id.uid);\n    }\n    //获取之前的消息\n    public MessageIterator newForwardMessageIterator(String conversationID, long firstMsgID) {\n        PeerId id = parsePeerId(conversationID);\n        return newCustomerPeerForwardMessageIterator(id.appid, id.uid, firstMsgID);\n    }\n    //获取之后的消息\n    public MessageIterator newBackwardMessageIterator(String conversationID, long msgID) {\n        PeerId id = parsePeerId(conversationID);\n        return newCustomerPeerBackwardMessageIterator(id.appid, id.uid, msgID);\n    }\n    //获取前后的消息\n    public MessageIterator newMiddleMessageIterator(String conversationID, long msgID) {\n        PeerId id = parsePeerId(conversationID);\n        return newCustomerPeerMiddleMessageIterator(id.appid, id.uid, msgID);\n    }\n\n    public boolean clearConversation(String conversationID) {\n        PeerId id = parsePeerId(conversationID);\n        return clearConversation(id.appid, id.uid);\n    }\n\n    public void saveMessageAttachment(IMessage msg, String address) {\n        Location loc = (Location)msg.content;\n        loc = Location.newLocation(loc.latitude, loc.longitude, address);\n        this.updateContent(msg.msgLocalID, loc.getRaw());\n    }\n\n    public void saveMessage(IMessage imsg) {\n        assert(imsg.isOutgoing);\n        ICustomerMessage m = (ICustomerMessage)imsg;\n        this.insertMessage(imsg, m.receiverAppID, m.receiver);\n    }\n\n    public void removeMessage(IMessage imsg) {\n        this.removeMessage(imsg.msgLocalID);\n    }\n\n\n    public void markMessageListened(IMessage imsg) {\n        this.markMessageListened(imsg.msgLocalID);\n    }\n\n\n    public void markMessageFailure(IMessage imsg) {\n        this.markMessageFailure(imsg.msgLocalID);\n    }\n\n\n    public void eraseMessageFailure(IMessage imsg) {\n        this.eraseMessageFailure(imsg.msgLocalID);\n    }\n\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/EPeerMessageDB.java",
    "content": "package com.beetle.bauhinia.db;\n\npublic class EPeerMessageDB extends BasePeerMessageDB {\n    private static EPeerMessageDB instance = new EPeerMessageDB();\n\n    public static EPeerMessageDB getInstance() {\n        return instance;\n    }\n\n    EPeerMessageDB() {\n        secret = 1;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/GroupMessageDB.java",
    "content": "package com.beetle.bauhinia.db;\n\nimport com.beetle.bauhinia.db.message.Location;\n\n/**\n * Created by houxh on 15/3/21.\n * FileGroupMessageDB vs SQLGroupMessageDB\n */\npublic class GroupMessageDB extends SQLGroupMessageDB implements IMessageDB {\n    public static final boolean SQL_ENGINE_DB = true;\n\n    private static GroupMessageDB instance = new GroupMessageDB();\n\n    public static GroupMessageDB getInstance() {\n        return instance;\n    }\n\n\n    public boolean clearConversation(String conversationID) {\n        long groupId = Long.parseLong(conversationID);\n        return clearConversation(groupId);\n    }\n\n    public void saveMessageAttachment(IMessage msg, String address) {\n        Location loc = (Location)msg.content;\n        loc = Location.newLocation(loc.latitude, loc.longitude, address);\n        this.updateContent(msg.msgLocalID, loc.getRaw());\n    }\n\n    public void saveMessage(IMessage imsg) {\n        this.insertMessage(imsg, imsg.receiver);\n    }\n\n\n    public void removeMessage(IMessage imsg) {\n        this.removeMessage(imsg.msgLocalID);\n    }\n\n\n    public void markMessageListened(IMessage imsg) {\n        this.markMessageListened(imsg.msgLocalID);\n    }\n\n\n    public void markMessageFailure(IMessage imsg) {\n        this.markMessageFailure(imsg.msgLocalID);\n    }\n\n\n    public void eraseMessageFailure(IMessage imsg) {\n        this.eraseMessageFailure(imsg.msgLocalID);\n    }\n\n\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/ICustomerMessage.java",
    "content": "package com.beetle.bauhinia.db;\n\n\n/**\n * Created by houxh on 14-7-22.\n */\n\n\npublic class ICustomerMessage  extends IMessage {\n    public long senderAppID;\n    public long receiverAppID;\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/IMessage.java",
    "content": "package com.beetle.bauhinia.db;\n\nimport com.beetle.bauhinia.db.message.*;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonObject;\nimport java.beans.PropertyChangeListener;\nimport java.beans.PropertyChangeSupport;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/*\n raw format\n {\n    \"text\":\"文本\",\n    \"image\":\"image url\",\n    \"image2\": {\n        \"url\":\"image url\",\n        \"width\":\"宽度(整数)\",\n        \"height\":\"高度(整数)\"\n    }\n    \"audio\": {\n        \"url\":\"audio url\",\n        \"duration\":\"时长(整数)\"\n    }\n    \"location\":{\n        \"latitude\":\"纬度(浮点数)\",\n        \"longitude\":\"经度(浮点数)\"\n    }\n    \"notification\":\"群组通知内容\"\n    \"link\":{\n        \"image\":\"图片url\",\n        \"url\":\"跳转url\",\n        \"title\":\"标题\"\n    }\n    \"video\":{\n         \"url\":\"视频url\",\n         \"thumbnail\":\"视频缩略图url\",\n         \"width\":\"宽度(整数)\",\n         \"height\":\"高度(整数)\",\n         \"duration\":\"时长(整数)\"\n     }\n     \"file\":{\n         \"url\":\"文件url\",\n         \"filename\":\"文件名称\",\n         \"size\":\"文件大小\"\n     }\n     \"revoke\": {\n        \"msgid\": \"被撤回消息的uuid\"\n     }\n     \"classroom\": {\n         \"master_id\": \"群课堂发起人(整数)\",\n         \"channel_id\": \"频道id\",\n         \"server_id\": \"频道对应的服务器id(整数)\"\n     }\n     \"readed\":{\n         \"msgid\":\"已读消息uuid\"\n     }\n}*/\n\n/**\n * Created by houxh on 14-7-22.\n */\n\npublic class IMessage implements Cloneable {\n    static Gson gson = new GsonBuilder().create();\n\n    public long msgLocalID;\n    public int flags;\n    public long sender;\n    public long receiver;\n    public MessageContent content;\n    public ArrayList<String> tags;\n    public int timestamp;//单位秒\n    public boolean secret;//点对点加密\n\n    public int readerCount;//群组消息已读数量\n    public int receiverCount;//未存储到数据库\n    public int referenceCount;//被引用的次数\n\n    //以下字段未保存在文件中\n    public boolean isOutgoing; //当前用户发出的消息\n    private String senderName;\n    private String senderAvatar;\n    private boolean uploading;\n    private boolean playing;\n    private boolean downloading;\n    private boolean geocoding;\n\n    public IMessage() {\n        tags = new ArrayList<>();\n    }\n\n    @Override\n    public Object clone() {\n        IMessage stu = null;\n        try{\n            stu = (IMessage)super.clone();\n            stu.content = (MessageContent)content.clone();\n        }catch(CloneNotSupportedException e) {\n            e.printStackTrace();\n        }\n        return stu;\n    }\n\n    public static MessageContent fromRaw(String raw) {\n        MessageContent  content;\n        try {\n            JsonObject element = gson.fromJson(raw, JsonObject.class);\n            if (element.has(MessageContent.TEXT)) {\n                content = gson.fromJson(raw, Text.class);\n            } else if (element.has(MessageContent.IMAGE2)) {\n                content = gson.fromJson(element.get(MessageContent.IMAGE2), Image.class);\n            } else if (element.has(MessageContent.AUDIO)) {\n                content = gson.fromJson(element.get(MessageContent.AUDIO), Audio.class);\n            } else if (element.has(MessageContent.NOTIFICATION)) {\n                content = GroupNotification.newGroupNotification(element.get(MessageContent.NOTIFICATION).getAsString());\n            } else if (element.has(MessageContent.LOCATION)) {\n                content = gson.fromJson(element.get(MessageContent.LOCATION), Location.class);\n            } else if (element.has(MessageContent.ATTACHMENT)) {\n                content = gson.fromJson(element.get(MessageContent.ATTACHMENT), Attachment.class);\n            } else if (element.has(MessageContent.LINK)) {\n                content = gson.fromJson(element.get(MessageContent.LINK), Link.class);\n            } else if (element.has(MessageContent.HEADLINE)) {\n                content = gson.fromJson(element.get(MessageContent.HEADLINE), Headline.class);\n            } else if (element.has(MessageContent.VOIP)) {\n                content = gson.fromJson(element.get(MessageContent.VOIP), VOIP.class);\n            } else if (element.has(MessageContent.GROUP_VOIP)) {\n                content = gson.fromJson(element.get(MessageContent.GROUP_VOIP), GroupVOIP.class);\n            } else if (element.has(MessageContent.P2P_SESSION)) {\n                content = gson.fromJson(element.get(MessageContent.P2P_SESSION), P2PSession.class);\n            } else if (element.has(MessageContent.SECRET)) {\n                content = gson.fromJson(element.get(MessageContent.SECRET), Secret.class);\n            } else if (element.has(MessageContent.FILE)) {\n                content = gson.fromJson(element.get(MessageContent.FILE), File.class);\n            } else if (element.has(MessageContent.VIDEO)) {\n                content = gson.fromJson(element.get(MessageContent.VIDEO), Video.class);\n            } else if (element.has(MessageContent.REVOKE)) {\n                content = gson.fromJson(element.get(MessageContent.REVOKE), Revoke.class);\n            } else if (element.has(MessageContent.READED)) {\n                content = gson.fromJson(element.get(MessageContent.READED), Readed.class);\n            } else if (element.has(MessageContent.ACK)) {\n                content = gson.fromJson(element.get(MessageContent.ACK), ACK.class);\n            } else if (element.has(MessageContent.CLASSROOM)) {\n                content = gson.fromJson(element.get(MessageContent.CLASSROOM), Classroom.class);\n            } else if (element.has(MessageContent.TAG)) {\n                content = gson.fromJson(element.get(MessageContent.TAG), Tag.class);\n            } else if (element.has(MessageContent.CONFERENCE)) {\n                content = gson.fromJson(element.get(MessageContent.CONFERENCE), Conference.class);\n            } else {\n                content = new Unknown();\n            }\n            if (element.has(\"uuid\")) {\n                content.setUUID(element.get(\"uuid\").getAsString());\n            }\n            if (element.has(\"group_id\")) {\n                content.setGroupId(element.get(\"group_id\").getAsLong());\n            }\n            if (element.has(\"reference\")) {\n                content.setReference(element.get(\"reference\").getAsString());\n            }\n            if(element.has(\"store_id\")) {\n                content.setStoreId(element.get(\"store_id\").getAsLong());\n            }\n            if (element.has(\"store_name\")) {\n                content.setStoreName(element.get(\"store_name\").getAsString());\n            }\n            if (element.has(\"session_id\")) {\n                content.setSessionId(element.get(\"session_id\").getAsString());\n            }\n            if (element.has(\"name\")) {\n                content.setName(element.get(\"name\").getAsString());\n            }\n            if (element.has(\"app_name\")) {\n                content.setAppName(element.get(\"app_name\").getAsString());\n            }\n\n        } catch (Exception e) {\n            content = new Unknown();\n        }\n\n        content.setRaw(raw);\n        return content;\n    }\n\n    public void setContent(String raw) {\n        content = fromRaw(raw);\n    }\n\n    public void setContent(MessageContent content) {\n        this.content = content;\n    }\n\n    public long getStoreId() {\n        if (this.content != null) {\n            return this.content.getStoreId();\n        } else {\n            return 0;\n        }\n    }\n\n    public String getUUID() {\n        if (this.content != null) {\n            return this.content.getUUID() != null ? this.content.getUUID() : \"\";\n        } else {\n            return \"\";\n        }\n    }\n\n    public String getReference() {\n        if (this.content != null) {\n            return this.content.getReference() != null ? this.content.getReference() : \"\";\n        } else {\n            return \"\";\n        }\n    }\n\n    public MessageContent.MessageType getType() {\n        if (content != null) {\n            return content.getType();\n        } else {\n            return MessageContent.MessageType.MESSAGE_UNKNOWN;\n        }\n    }\n\n    private PropertyChangeSupport changeSupport = new PropertyChangeSupport(\n            this);\n\n    public void addPropertyChangeListener(PropertyChangeListener listener) {\n        changeSupport.addPropertyChangeListener(listener);\n    }\n\n    public void removePropertyChangeListener(PropertyChangeListener listener) {\n        changeSupport.removePropertyChangeListener(listener);\n    }\n\n    public void addPropertyChangeListener(String propertyName,\n                                          PropertyChangeListener listener) {\n        changeSupport.addPropertyChangeListener(propertyName, listener);\n    }\n\n    public void setUploading(boolean uploading) {\n        boolean old = this.uploading;\n        this.uploading = uploading;\n        changeSupport.firePropertyChange(\"uploading\", old, this.uploading);\n    }\n\n    public boolean getUploading() {\n        return this.uploading;\n    }\n\n    public void setPlaying(boolean playing) {\n        boolean old = this.playing;\n        this.playing = playing;\n        changeSupport.firePropertyChange(\"playing\", old, this.playing);\n    }\n\n    public boolean getPlaying() {\n        return this.playing;\n    }\n\n    public void setDownloading(boolean downloading) {\n        boolean old = this.downloading;\n        this.downloading = downloading;\n        changeSupport.firePropertyChange(\"downloading\", old, this.downloading);\n    }\n\n    public boolean getDownloading() {\n        return this.downloading;\n    }\n\n    public void setFlags(int f) {\n        int old = flags;\n        flags = f;\n        changeSupport.firePropertyChange(\"flags\", old, flags);\n    }\n\n    public boolean isFailure() {\n        return (flags & MessageFlag.MESSAGE_FLAG_FAILURE) != 0;\n    }\n\n    public void setFailure(boolean f) {\n        boolean old = isFailure();\n        if (f) {\n            flags = flags | MessageFlag.MESSAGE_FLAG_FAILURE;\n        } else {\n            flags = flags & (~MessageFlag.MESSAGE_FLAG_FAILURE);\n        }\n        changeSupport.firePropertyChange(\"failure\", old, f);\n    }\n\n    public boolean isAck() {\n        return (flags & MessageFlag.MESSAGE_FLAG_ACK) != 0;\n    }\n\n    public void setAck(boolean ack) {\n        boolean old = isAck();\n        if (ack) {\n            flags = flags | MessageFlag.MESSAGE_FLAG_ACK;\n        } else {\n            flags = flags & (~MessageFlag.MESSAGE_FLAG_ACK);\n        }\n        changeSupport.firePropertyChange(\"ack\", old, ack);\n    }\n\n    public boolean isListened() {\n        return (flags & MessageFlag.MESSAGE_FLAG_LISTENED) != 0;\n    }\n\n\n    public void setListened(boolean listened) {\n        boolean old = isListened();\n        if (listened) {\n            flags = flags | MessageFlag.MESSAGE_FLAG_LISTENED;\n        } else {\n            flags = flags & (~MessageFlag.MESSAGE_FLAG_LISTENED);\n        }\n        changeSupport.firePropertyChange(\"listened\", old, listened);\n    }\n\n\n    public void setReaded(boolean readed) {\n        boolean old = isReaded();\n        if (readed) {\n            flags = flags | MessageFlag.MESSAGE_FLAG_READED;\n        } else {\n            flags = flags & (~MessageFlag.MESSAGE_FLAG_READED);\n        }\n        changeSupport.firePropertyChange(\"readed\", old, readed);\n    }\n\n    public boolean isReaded() {\n        return (flags & MessageFlag.MESSAGE_FLAG_READED) != 0;\n    }\n\n\n    public boolean getGeocoding() {\n        return this.geocoding;\n    }\n\n    public void setGeocoding(boolean geocoding) {\n        boolean old = this.geocoding;\n        this.geocoding = geocoding;\n        changeSupport.firePropertyChange(\"geocoding\", old, geocoding);\n    }\n\n    public void setSenderName(String senderName) {\n        String old = this.senderName;\n        this.senderName = senderName;\n        changeSupport.firePropertyChange(\"senderName\", old, this.senderName);\n    }\n\n    public String getSenderName() {\n        return this.senderName;\n    }\n\n    public void setSenderAvatar(String senderAvatar) {\n        String old = this.senderAvatar;\n        this.senderAvatar = senderAvatar;\n        changeSupport.firePropertyChange(\"senderAvatar\", old, this.senderAvatar);\n    }\n\n    public String getSenderAvatar() {\n        return this.senderAvatar;\n    }\n\n    public int getReaderCount() {\n        return this.readerCount;\n    }\n\n    public void setReaderCount(int count) {\n        int old = this.readerCount;\n        this.readerCount = count;\n        changeSupport.firePropertyChange(\"readerCount\", old, this.readerCount);\n    }\n\n    public int getReferenceCount() {\n        return this.referenceCount;\n    }\n\n    public void setReferenceCount(int count) {\n        int old = this.referenceCount;\n        this.referenceCount = count;\n        changeSupport.firePropertyChange(\"referenceCount\", old, this.referenceCount);\n    }\n\n    public void addTag(String tag) {\n        if (!tags.contains(tag)) {\n            tags.add(tag);\n            changeSupport.firePropertyChange(\"tags\", null, this.tags);\n        }\n    }\n\n    public void deleteTag(String tag) {\n        if (tags.contains(tag)) {\n            tags.remove(tag);\n            changeSupport.firePropertyChange(\"tags\", null, this.tags);\n        }\n    }\n\n\n    public List<String> getTags() {\n        return this.tags;\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/IMessageDB.java",
    "content": "package com.beetle.bauhinia.db;\n\n/**\n * Created by houxh on 2017/11/13.\n */\n\npublic interface IMessageDB {\n\n    //获取最近的消息\n    MessageIterator newMessageIterator(long conversationID);\n    //获取之前的消息\n    MessageIterator newForwardMessageIterator(long conversationID, long firstMsgID);\n    //获取之后的消息\n    MessageIterator newBackwardMessageIterator(long conversationID, long msgID);\n    //获取前后的消息\n    MessageIterator newMiddleMessageIterator(long conversationID, long msgID);\n\n    boolean clearConversation(String conversationID);\n    void saveMessageAttachment(IMessage msg, String address);\n    void saveMessage(IMessage imsg);\n    void removeMessage(IMessage imsg);\n    void markMessageListened(IMessage imsg);\n    void markMessageFailure(IMessage imsg);\n    void eraseMessageFailure(IMessage imsg);\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/MessageFlag.java",
    "content": "package com.beetle.bauhinia.db;\n\n\n\npublic class MessageFlag {\n    public static final int MESSAGE_FLAG_DELETE = 1;\n    public static final int MESSAGE_FLAG_ACK = 2;\n    //public static final int MESSAGE_FLAG_PEER_ACK = 4;\n    public static final int MESSAGE_FLAG_FAILURE = 8;\n    public static final int MESSAGE_FLAG_LISTENED = 16;\n    public static final int MESSAGE_FLAG_READED = 32;\n}"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/MessageIterator.java",
    "content": "package com.beetle.bauhinia.db;\n\nimport android.util.Log;\n\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\n\n/**\n * Created by houxh on 15/3/21.\n */\npublic interface MessageIterator {\n    public IMessage next();\n}\n\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/PeerMessageDB.java",
    "content": "package com.beetle.bauhinia.db;\nimport com.beetle.bauhinia.db.message.Location;\n\nclass BasePeerMessageDB extends SQLPeerMessageDB implements IMessageDB {\n\n    public boolean clearConversation(String conversationID) {\n        long peer = Long.parseLong(conversationID);\n        return clearConversation(peer);\n    }\n\n    public void saveMessageAttachment(IMessage msg, String address) {\n        Location loc = (Location)msg.content;\n        loc = Location.newLocation(loc.latitude, loc.longitude, address);\n        this.updateContent(msg.msgLocalID, loc.getRaw());\n    }\n\n    public void saveMessage(IMessage imsg) {\n        assert(imsg.isOutgoing);\n        this.insertMessage(imsg, imsg.receiver);\n    }\n\n    public void removeMessage(IMessage imsg) {\n        this.removeMessage(imsg.msgLocalID);\n    }\n\n    public void markMessageListened(IMessage imsg) {\n        this.markMessageListened(imsg.msgLocalID);\n    }\n\n    public void markMessageFailure(IMessage imsg) {\n        this.markMessageFailure(imsg.msgLocalID);\n    }\n\n    public void eraseMessageFailure(IMessage imsg) {\n        this.eraseMessageFailure(imsg.msgLocalID);\n    }\n}\n\n\npublic class PeerMessageDB extends BasePeerMessageDB {\n    private static PeerMessageDB instance = new PeerMessageDB();\n\n    public static PeerMessageDB getInstance() {\n        return instance;\n    }\n\n\n    PeerMessageDB() {\n        secret = 0;\n    }\n}"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/SQLCustomerMessageDB.java",
    "content": "package com.beetle.bauhinia.db;\n\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.text.TextUtils;\n\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Text;\n\nimport java.util.ArrayList;\n\n/**\n * Created by houxh on 16/1/18.\n */\npublic class SQLCustomerMessageDB {\n\n    private class CustomerMessageIterator implements MessageIterator{\n        protected Cursor cursor;\n\n        public CustomerMessageIterator() {}\n\n        public CustomerMessageIterator(SQLiteDatabase db, long storeID) {\n            String sql = \"SELECT id, peer_appid, peer, store_id, sender_appid, sender, receiver_appid, receiver, timestamp, flags, content FROM customer_message WHERE store_id = ? ORDER BY id DESC\";\n            this.cursor = db.rawQuery(sql, new String[]{\"\"+storeID});\n        }\n\n        public CustomerMessageIterator(SQLiteDatabase db, long storeID, long lastMsgID) {\n            String sql = \"SELECT id, peer_appid, peer, store_id, sender_appid, sender, receiver_appid, receiver, timestamp, flags, content FROM customer_message WHERE store_id = ? AND id < ? ORDER BY id DESC\";\n            this.cursor = db.rawQuery(sql, new String[]{\"\"+storeID, \"\"+lastMsgID});\n        }\n\n\n        public IMessage next() {\n            if (cursor == null) {\n                return null;\n            }\n            boolean r = cursor.moveToNext();\n            if (!r) {\n                cursor.close();\n                cursor = null;\n                return null;\n            }\n            ICustomerMessage msg = getMessage(cursor);\n            return msg;\n        }\n    }\n\n    private class CustomerPeerMessageIterator extends CustomerMessageIterator {\n        public CustomerPeerMessageIterator(SQLiteDatabase db, long peerAppID, long peer) {\n            String sql = \"SELECT id, peer_appid, peer, store_id, sender_appid, sender, receiver_appid, receiver, timestamp, flags, content FROM customer_message WHERE peer_appid = ? AND peer = ? ORDER BY id DESC\";\n            this.cursor = db.rawQuery(sql, new String[]{\"\"+peerAppID, \"\"+peer});\n        }\n\n        public CustomerPeerMessageIterator(SQLiteDatabase db, long peerAppID, long peer, long lastMsgID) {\n            String sql = \"SELECT id, peer_appid, peer, store_id, sender_appid, sender, receiver_appid, receiver, timestamp, flags, content FROM customer_message WHERE peer_appid = ? AND peer = ? AND id < ? ORDER BY id DESC\";\n            this.cursor = db.rawQuery(sql, new String[]{\"\"+peerAppID, \"\"+peer, \"\"+lastMsgID});\n        }\n    }\n\n\n\n\n    private static final String TABLE_NAME = \"customer_message\";\n    private static final String FTS_TABLE_NAME = \"customer_message_fts\";\n    private static final String TAG = \"beetle\";\n\n    private SQLiteDatabase db;\n\n    public void setDb(SQLiteDatabase db) {\n        this.db = db;\n    }\n\n    public SQLiteDatabase getDb() {\n        return this.db;\n    }\n\n    private boolean insertFTS(long msgLocalID, String text) {\n        String t = tokenizer(text);\n        ContentValues values = new ContentValues();\n        values.put(\"docid\", msgLocalID);\n        values.put(\"content\", t);\n        db.insert(\"customer_message_fts\", null, values);\n        return true;\n    }\n\n\n    public boolean insertMessage(IMessage m, long peerAppID, long peer) {\n        ICustomerMessage msg = (ICustomerMessage)m;\n        ContentValues values = new ContentValues();\n        values.put(\"peer_appid\", peerAppID);\n        values.put(\"peer\", peer);\n        values.put(\"sender_appid\", msg.senderAppID);\n        values.put(\"sender\", msg.sender);\n        values.put(\"store_id\", msg.getStoreId());\n        values.put(\"receiver_appid\", msg.receiverAppID);\n        values.put(\"receiver\", msg.receiver);\n        values.put(\"timestamp\", msg.timestamp);\n        values.put(\"flags\", msg.flags);\n        if (!TextUtils.isEmpty(msg.getUUID())) {\n            values.put(\"uuid\", msg.getUUID());\n        }\n        values.put(\"content\", msg.content.getRaw());\n        long id = db.insert(TABLE_NAME, null, values);\n        if (id == -1) {\n\n            return  false;\n        }\n        msg.msgLocalID = id;\n\n        if (msg.content.getType() == MessageContent.MessageType.MESSAGE_TEXT) {\n            Text text = (Text)msg.content;\n            insertFTS((int)id, text.text);\n        }\n        return true;\n    }\n\n    public boolean updateContent(long msgLocalID, String content) {\n        ContentValues cv = new ContentValues();\n        cv.put(\"content\", content);\n        int rows = db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        return rows == 1;\n    }\n\n\n    public boolean acknowledgeMessage(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_ACK);\n    }\n\n    public boolean markMessageFailure(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_FAILURE);\n    }\n\n    public boolean markMessageListened(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_LISTENED);\n    }\n\n    public boolean eraseMessageFailure(long msgLocalID) {\n        int f = MessageFlag.MESSAGE_FLAG_FAILURE;\n        return removeFlag(msgLocalID, f);\n    }\n\n    public boolean addFlag(long msgLocalID, int f) {\n        String sql = \"SELECT flags FROM customer_message WHERE id=?\";\n        Cursor cursor = db.rawQuery(sql, new String[]{\"\"+msgLocalID});\n        if (cursor.moveToNext()) {\n            int flags = cursor.getInt(cursor.getColumnIndex(\"flags\"));\n            flags |= f;\n\n            ContentValues cv = new ContentValues();\n            cv.put(\"flags\", flags);\n            db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        }\n        cursor.close();\n        return true;\n    }\n\n    public boolean removeFlag(long msgLocalID, int f) {\n        String sql = \"SELECT flags FROM customer_message WHERE id=?\";\n        Cursor cursor = db.rawQuery(sql, new String[]{\"\"+msgLocalID});\n        if (cursor.moveToNext()) {\n            int flags = cursor.getInt(cursor.getColumnIndex(\"flags\"));\n            flags &= ~f;\n            ContentValues cv = new ContentValues();\n            cv.put(\"flags\", flags);\n            db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        }\n        cursor.close();\n        return true;\n    }\n\n    public boolean updateFlag(long msgLocalID, int flags) {\n        ContentValues cv = new ContentValues();\n        cv.put(\"flags\", flags);\n        db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        return true;\n    }\n\n    public boolean removeMessage(long msgLocalID) {\n        db.delete(TABLE_NAME, \"id = ?\", new String[]{\"\"+msgLocalID});\n        db.delete(FTS_TABLE_NAME, \"rowid = ?\", new String[]{\"\"+msgLocalID});\n        return true;\n    }\n\n    public boolean removeMessageIndex(long msgLocalID) {\n        db.delete(FTS_TABLE_NAME, \"rowid = ?\", new String[]{\"\"+msgLocalID});\n        return true;\n    }\n\n\n    public boolean clearConversation(long storeID) {\n        db.delete(TABLE_NAME, \"store_id = ?\", new String[]{\"\"+storeID});\n        return true;\n    }\n\n    public boolean clearConversation(long appid, long uid) {\n        db.delete(TABLE_NAME, \"peer_appid = ? AND peer = ?\", new String[]{\"\"+appid, \"\"+uid});\n        return true;\n    }\n\n    public MessageIterator newMessageIterator(long storeID) {\n        return new CustomerMessageIterator(db, storeID);\n    }\n\n    public MessageIterator newForwardMessageIterator(long storeID, long firstMsgID) {\n        return new CustomerMessageIterator(db, storeID, firstMsgID);\n    }\n\n    public MessageIterator newBackwardMessageIterator(long storeID, long msgID) {\n        return null;\n    }\n\n    public MessageIterator newMiddleMessageIterator(long storeID, long msgID) {\n        return null;\n    }\n\n\n    public MessageIterator newCustomerPeerMessageIterator(long appid, long uid) {\n        return new CustomerPeerMessageIterator(db, appid, uid);\n    }\n\n    public MessageIterator newCustomerPeerForwardMessageIterator(long appid, long uid, long firstMsgID) {\n        return new CustomerPeerMessageIterator(db, appid, uid, firstMsgID);\n    }\n\n    public MessageIterator newCustomerPeerBackwardMessageIterator(long appid, long uid, long msgID) {\n        return null;\n    }\n\n    public MessageIterator newCustomerPeerMiddleMessageIterator(long appid, long uid, long msgID) {\n        return null;\n    }\n\n\n    private String tokenizer(String key) {\n        StringBuilder builder = new StringBuilder();\n\n        for (int i = 0; i < key.length(); i++) {\n            char c = key.charAt(i);\n            builder.append(c);\n            if (c >= 0x4e00 && c <= 0x9fff) {\n                builder.append(' ');\n            }\n        }\n        return builder.toString();\n    }\n\n    public ArrayList<IMessage> search(String key) {\n        key = key.replace(\"'\", \"\\'\");\n        String k = this.tokenizer(key);\n\n        Cursor cursor = db.query(FTS_TABLE_NAME, new String[]{\"rowid\"},\n                \"content MATCH(?)\",  new String[]{k},\n                null, null, null);\n        ArrayList<Long> rows = new ArrayList<Long>();\n        while(cursor.moveToNext()) {\n            long rowid = cursor.getInt(cursor.getColumnIndex(\"rowid\"));\n            rows.add(rowid);\n        }\n        cursor.close();\n\n        ArrayList<IMessage> messages = new ArrayList<IMessage>();\n        for (int i = 0; i < rows.size(); i++) {\n            IMessage msg = getMessage(rows.get(i));\n            messages.add(msg);\n        }\n        return messages;\n    }\n\n\n    private ICustomerMessage getMessage(Cursor cursor) {\n        ICustomerMessage msg = new ICustomerMessage();\n        msg.msgLocalID = cursor.getLong(cursor.getColumnIndex(\"id\"));\n        msg.senderAppID = cursor.getLong(cursor.getColumnIndex(\"sender_appid\"));\n        msg.sender = cursor.getLong(cursor.getColumnIndex(\"sender\"));\n        msg.receiverAppID = cursor.getLong(cursor.getColumnIndex(\"receiver_appid\"));\n        msg.receiver = cursor.getLong(cursor.getColumnIndex(\"receiver\"));\n        msg.timestamp = cursor.getInt(cursor.getColumnIndex(\"timestamp\"));\n        msg.flags = cursor.getInt(cursor.getColumnIndex(\"flags\"));\n        String content = cursor.getString(cursor.getColumnIndex(\"content\"));\n        msg.setContent(content);\n        return msg;\n    }\n\n    public ICustomerMessage getMessage(long id) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender_appid\", \"sender\", \"store_id\", \"receiver_appid\", \"receiver\", \"timestamp\", \"flags\", \"content\"},\n                \"id = ?\", new String[]{\"\"+id}, null, null, null);\n        ICustomerMessage msg = null;\n        if (cursor.moveToNext()) {\n            msg = getMessage(cursor);\n        }\n        cursor.close();\n        return msg;\n    }\n\n    public long getMessageId(String uuid) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\"},\n                \"uuid = ?\", new String[]{uuid}, null,null,null);\n        boolean r = cursor.moveToNext();\n        if (!r) {\n            cursor.close();\n            return 0;\n        }\n\n        long msgLocalId = cursor.getLong(cursor.getColumnIndex(\"id\"));\n        cursor.close();\n        return msgLocalId;\n    }\n\n    public ICustomerMessage getMessage(String uuid) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender_appid\", \"sender\", \"store_id\", \"receiver_appid\", \"receiver\", \"timestamp\", \"flags\", \"content\"},\n                \"uuid = ?\", new String[]{uuid}, null, null, null);\n        ICustomerMessage msg = null;\n        if (cursor.moveToNext()) {\n            msg = getMessage(cursor);\n        }\n        cursor.close();\n        return msg;\n    }\n\n    public ICustomerMessage getLastMessage(long storeID) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender_appid\", \"sender\", \"store_id\", \"receiver_appid\", \"receiver\", \"timestamp\", \"flags\", \"content\"},\n                \"store_id = ?\", new String[]{\"\"+storeID}, null, null, \"id DESC\");\n        ICustomerMessage msg = null;\n        if (cursor.moveToNext()) {\n            msg = getMessage(cursor);\n        }\n        cursor.close();\n        return msg;\n\n    }\n\n    public ICustomerMessage getLastMessage(long appid, long uid) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender_appid\", \"sender\", \"store_id\", \"receiver_appid\", \"receiver\", \"timestamp\", \"flags\", \"content\"},\n                \"peer_appid = ? AND peer = ?\", new String[]{\"\"+appid, \"\" + uid}, null, null, \"id DESC\");\n        ICustomerMessage msg = null;\n        if (cursor.moveToNext()) {\n            msg = getMessage(cursor);\n        }\n        cursor.close();\n        return msg;\n\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/SQLGroupMessageDB.java",
    "content": "package com.beetle.bauhinia.db;\n\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.SQLException;\nimport android.database.sqlite.SQLiteConstraintException;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.text.TextUtils;\n\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Text;\n\nimport java.lang.reflect.Array;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n\n/**\n * Created by houxh on 15/3/21.\n */\npublic class SQLGroupMessageDB  {\n    private class GroupMessageIterator implements MessageIterator{\n        protected Cursor cursor;\n        public IMessage next() {\n            if (cursor == null) {\n                return null;\n            }\n            boolean r = cursor.moveToNext();\n            if (!r) {\n                cursor.close();\n                cursor = null;\n                return null;\n            }\n            IMessage msg = getMessage(cursor);\n            return msg;\n        }\n    }\n\n\n    private class ForwardGroupMessageIterator extends GroupMessageIterator {\n        public ForwardGroupMessageIterator(SQLiteDatabase db, long group_id)  {\n            Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"group_id\", \"timestamp\", \"flags\", \"reader_count\", \"reference_count\", \"content\", \"tags\"},\n                    \"group_id = ?\", new String[]{\"\"+group_id}, null, null, \"id DESC\");\n            this.cursor = cursor;\n        }\n\n        public ForwardGroupMessageIterator(SQLiteDatabase db, long group_id, long lastMsgID) {\n            Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"group_id\", \"timestamp\", \"flags\", \"reader_count\", \"reference_count\", \"content\", \"tags\"},\n                    \"group_id=? AND id < ?\", new String[]{\"\"+group_id, \"\"+lastMsgID}, null, null, \"id DESC\");\n            this.cursor = cursor;\n        }\n    }\n\n    private class BackwarkGroupMessageIterator extends GroupMessageIterator {\n        public BackwarkGroupMessageIterator(SQLiteDatabase db, long group_id, long lastMsgID) {\n            Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"group_id\", \"timestamp\", \"flags\", \"reader_count\", \"reference_count\", \"content\", \"tags\"},\n                    \"group_id=? AND id > ?\", new String[]{\"\"+group_id, \"\"+lastMsgID}, null, null, \"id ASC\");\n            this.cursor = cursor;\n\n\n        }\n    }\n\n    private class MiddleGroupMessageIterator extends GroupMessageIterator {\n        public MiddleGroupMessageIterator(SQLiteDatabase db, long group_id, long lastMsgID) {\n            Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"group_id\", \"timestamp\", \"flags\", \"reader_count\", \"reference_count\", \"content\", \"tags\"},\n                    \"group_id=? AND id > ? AND id < ?\", new String[]{\"\"+group_id, \"\"+(lastMsgID-10), \"\" + (lastMsgID+10)}, null, null, \"id DESC\");\n            this.cursor = cursor;\n        }\n    }\n\n    private class TopicGroupMessageIterator extends GroupMessageIterator {\n        public TopicGroupMessageIterator(SQLiteDatabase db, long group_id, String uuid) {\n            Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"group_id\", \"timestamp\", \"flags\", \"reader_count\", \"reference_count\", \"content\", \"tags\"},\n                    \"group_id=? AND reference = ?\", new String[]{\"\"+group_id, uuid}, null, null, \"id DESC\");\n            this.cursor = cursor;\n        }\n    }\n\n\n    public class GroupConversationIterator implements ConversationIterator {\n        private Cursor cursor;\n\n        public GroupConversationIterator(SQLiteDatabase db) {\n            this.cursor = db.rawQuery(\"SELECT MAX(id) as id, group_id FROM group_message GROUP BY group_id\", null);\n        }\n\n        public IMessage next() {\n            if (cursor == null) {\n                return null;\n            }\n\n            boolean r = cursor.moveToNext();\n            if (!r) {\n                cursor.close();\n                cursor = null;\n                return null;\n            }\n\n            long id = cursor.getLong(cursor.getColumnIndex(\"id\"));\n            IMessage msg = getMessage(id);\n            return msg;\n        }\n\n    }\n\n\n    private static final String TABLE_NAME = \"group_message\";\n    private static final String FTS_TABLE_NAME = \"group_message_fts\";\n    private static final String READED_TABLE_NAME = \"group_message_readed\";\n    private static final String TAG = \"beetle\";\n\n    private SQLiteDatabase db;\n\n    public void setDb(SQLiteDatabase db) {\n        this.db = db;\n    }\n\n    public SQLiteDatabase getDb() {\n        return this.db;\n    }\n\n    private boolean insertFTS(int msgLocalID, String text) {\n        String t = tokenizer(text);\n        ContentValues values = new ContentValues();\n        values.put(\"docid\", msgLocalID);\n        values.put(\"content\", t);\n        db.insert(\"group_message_fts\", null, values);\n        return true;\n    }\n\n    boolean incrementReferenceCount(String uuid) {\n        try {\n            db.execSQL(\"UPDATE group_message SET reference_count=reference_count+1 WHERE uuid=?\", new String[]{uuid});\n            return true;\n        } catch (SQLException e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    public boolean insertMessage(IMessage msg, long gid) {\n        ContentValues values = new ContentValues();\n        values.put(\"sender\", msg.sender);\n        values.put(\"group_id\", msg.receiver);\n        values.put(\"timestamp\", msg.timestamp);\n        values.put(\"flags\", msg.flags);\n        if (!TextUtils.isEmpty(msg.getUUID())) {\n            values.put(\"uuid\", msg.getUUID());\n        }\n        if (!TextUtils.isEmpty(msg.getReference())) {\n            values.put(\"reference\", msg.getReference());\n        }\n        values.put(\"content\", msg.content.getRaw());\n        long id = db.insert(TABLE_NAME, null, values);\n        if (id == -1) {\n            return  false;\n        }\n        msg.msgLocalID = id;\n        if (msg.content.getType() == MessageContent.MessageType.MESSAGE_TEXT) {\n            Text text = (Text)msg.content;\n            insertFTS((int)id, text.text);\n        }\n        if (!TextUtils.isEmpty(msg.getReference())) {\n            incrementReferenceCount(msg.getReference());\n        }\n        return true;\n    }\n\n    public boolean insertMessages(List<IMessage> msgs) {\n        db.beginTransaction();\n        try {\n            for (IMessage msg : msgs) {\n                ContentValues values = new ContentValues();\n                values.put(\"sender\", msg.sender);\n                values.put(\"group_id\", msg.receiver);\n                values.put(\"timestamp\", msg.timestamp);\n                values.put(\"flags\", msg.flags);\n                if (!TextUtils.isEmpty(msg.getUUID())) {\n                    values.put(\"uuid\", msg.getUUID());\n                }\n                if (!TextUtils.isEmpty(msg.getReference())) {\n                    values.put(\"reference\", msg.getReference());\n                }\n                values.put(\"content\", msg.content.getRaw());\n\n                long id = db.insert(TABLE_NAME, null, values);\n                if (id == -1) {\n                    return false;\n                }\n                msg.msgLocalID = id;\n                if (msg.content.getType() == MessageContent.MessageType.MESSAGE_TEXT) {\n                    Text text = (Text) msg.content;\n                    insertFTS((int) id, text.text);\n                }\n                if (!TextUtils.isEmpty(msg.getReference())) {\n                    incrementReferenceCount(msg.getReference());\n                }\n            }\n            db.setTransactionSuccessful();\n            return true;\n        } finally {\n            db.endTransaction();\n        }\n    }\n\n    public boolean updateContent(long msgLocalID, String content) {\n        ContentValues cv = new ContentValues();\n        cv.put(\"content\", content);\n        int rows = db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        return rows == 1;\n    }\n\n\n    public int acknowledgeMessage(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_ACK);\n    }\n\n    public int markMessageFailure(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_FAILURE);\n    }\n\n    public int markMessageListened(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_LISTENED);\n    }\n\n    public int markMessageReaded(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_READED);\n    }\n\n    public boolean eraseMessageFailure(long msgLocalID) {\n        int f = MessageFlag.MESSAGE_FLAG_FAILURE;\n        return removeFlag(msgLocalID, f);\n    }\n\n    public int addFlag(long msgLocalID, int f) {\n        int r = 0;\n        String sql = \"SELECT flags FROM group_message WHERE id=?\";\n        Cursor cursor = db.rawQuery(sql, new String[]{\"\"+msgLocalID});\n        if (cursor.moveToNext()) {\n            int flags = cursor.getInt(cursor.getColumnIndex(\"flags\"));\n            if ((flags & f) == 0) {\n                flags |= f;\n\n                ContentValues cv = new ContentValues();\n                cv.put(\"flags\", flags);\n                r = db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\" + msgLocalID});\n            }\n        }\n        cursor.close();\n        return r;\n    }\n\n    private boolean removeFlag(long msgLocalID, int f) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"flags\"},\n                \"id = ?\", new String[]{\"\"+msgLocalID}, null, null, null);\n        if (cursor.moveToNext()) {\n            int flags = cursor.getInt(cursor.getColumnIndex(\"flags\"));\n            flags &= ~f;\n            ContentValues cv = new ContentValues();\n            cv.put(\"flags\", flags);\n            db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        }\n        cursor.close();\n        return true;\n    }\n\n    public boolean updateFlag(long msgLocalID, int flags) {\n        ContentValues cv = new ContentValues();\n        cv.put(\"flags\", flags);\n        db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        return true;\n    }\n\n    public boolean removeMessage(long msgLocalID) {\n        db.delete(TABLE_NAME, \"id = ?\", new String[]{\"\"+msgLocalID});\n        db.delete(FTS_TABLE_NAME, \"rowid = ?\", new String[]{\"\"+msgLocalID});\n        return true;\n    }\n\n    public boolean removeMessageIndex(long msgLocalID, long gid) {\n        db.delete(FTS_TABLE_NAME, \"rowid = ?\", new String[]{\"\"+msgLocalID});\n        return true;\n    }\n\n\n    public boolean clearConversation(long gid) {\n        db.delete(TABLE_NAME, \"group_id = ?\", new String[]{\"\"+gid});\n        return true;\n    }\n\n    public MessageIterator newMessageIterator(long gid) {\n        return new ForwardGroupMessageIterator(db, gid);\n    }\n\n    public MessageIterator newForwardMessageIterator(long gid, long firstMsgID) {\n        return new ForwardGroupMessageIterator(db, gid, firstMsgID);\n    }\n\n    public MessageIterator newBackwardMessageIterator(long gid, long msgID) {\n        return new BackwarkGroupMessageIterator(db, gid, msgID);\n    }\n\n    public MessageIterator newMiddleMessageIterator(long gid, long msgID) {\n        return new MiddleGroupMessageIterator(db, gid, msgID);\n    }\n\n    public MessageIterator newTopicMessageIterator(long gid, String uuid) {\n        return new TopicGroupMessageIterator(db, gid, uuid);\n    }\n\n    public ConversationIterator newConversationIterator() {\n        return new GroupConversationIterator(db);\n    }\n\n\n\n    private String tokenizer(String key) {\n        StringBuilder builder = new StringBuilder();\n\n        for (int i = 0; i < key.length(); i++) {\n            char c = key.charAt(i);\n            builder.append(c);\n            if (c >= 0x4e00 && c <= 0x9fff) {\n                builder.append(' ');\n            }\n        }\n        return builder.toString();\n    }\n\n    public ArrayList<IMessage> search(String key) {\n        key = key.replace(\"'\", \"\\'\");\n        String k = this.tokenizer(key);\n\n        Cursor cursor = db.query(FTS_TABLE_NAME, new String[]{\"rowid\"},\n                \"content MATCH(?)\",  new String[]{k},\n                null, null, null);\n        ArrayList<Long> rows = new ArrayList<Long>();\n        while(cursor.moveToNext()) {\n            long rowid = cursor.getInt(cursor.getColumnIndex(\"rowid\"));\n            rows.add(rowid);\n        }\n        cursor.close();\n\n        ArrayList<IMessage> messages = new ArrayList<IMessage>();\n        for (int i = 0; i < rows.size(); i++) {\n            IMessage msg = getMessage(rows.get(i));\n            if (msg != null) {\n                messages.add(msg);\n            }\n        }\n        return messages;\n    }\n\n    private IMessage getMessage(Cursor cursor) {\n        IMessage msg = new IMessage();\n        msg.msgLocalID = cursor.getLong(cursor.getColumnIndex(\"id\"));\n        msg.sender = cursor.getLong(cursor.getColumnIndex(\"sender\"));\n        msg.receiver = cursor.getLong(cursor.getColumnIndex(\"group_id\"));\n        msg.timestamp = cursor.getInt(cursor.getColumnIndex(\"timestamp\"));\n        msg.flags = cursor.getInt(cursor.getColumnIndex(\"flags\"));\n        msg.readerCount = cursor.getInt(cursor.getColumnIndex(\"reader_count\"));\n        msg.referenceCount = cursor.getInt(cursor.getColumnIndex(\"reference_count\"));\n        String content = cursor.getString(cursor.getColumnIndex(\"content\"));\n        msg.setContent(content);\n\n        String tags = cursor.getString(cursor.getColumnIndex(\"tags\"));\n        if (!TextUtils.isEmpty(tags)) {\n            String[] msgTags = tags.split(\",\");\n            for (int i = 0; i < msgTags.length; i++) {\n                msg.addTag(msgTags[i]);\n            }\n        }\n        return msg;\n    }\n\n    public IMessage getMessage(long id) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"group_id\", \"timestamp\", \"flags\", \"reader_count\", \"reference_count\", \"content\", \"tags\"},\n                \"id = ?\", new String[]{\"\"+id}, null, null, null);\n\n        IMessage msg = null;\n        if (cursor.moveToNext()) {\n            msg = getMessage(cursor);\n        }\n        cursor.close();\n        return msg;\n    }\n\n    public IMessage getMessage(String uuid) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"group_id\", \"timestamp\", \"flags\", \"reader_count\", \"reference_count\", \"content\", \"tags\"},\n                \"uuid = ?\", new String[]{uuid}, null, null, null);\n        IMessage msg = null;\n        if (cursor.moveToNext()) {\n            msg = getMessage(cursor);\n        }\n        cursor.close();\n        return msg;\n    }\n\n    public IMessage getLastMessage(long gid) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"group_id\", \"timestamp\", \"flags\", \"reader_count\", \"reference_count\", \"content\", \"tags\"},\n                \"group_id = ?\", new String[]{\"\"+gid}, null, null, \"id DESC\");\n        boolean r = cursor.moveToNext();\n        if (!r) {\n            cursor.close();\n            return null;\n        }\n\n        IMessage msg = getMessage(cursor);\n        cursor.close();\n        return msg;\n    }\n\n    public long getMessageId(String uuid) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\"},\n                \"uuid = ?\", new String[]{uuid}, null,null,null);\n        boolean r = cursor.moveToNext();\n        if (!r) {\n            cursor.close();\n            return 0;\n        }\n\n        long msgLocalId = cursor.getLong(cursor.getColumnIndex(\"id\"));\n        cursor.close();\n        return msgLocalId;\n    }\n\n    public boolean addMessageTag(long msgId, String tag) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"tags\"},\n                \"id = ?\", new String[]{\"\" + msgId}, null,null,null);\n        if (cursor.moveToNext()) {\n            String tags = cursor.getString(cursor.getColumnIndex(\"tags\"));\n            tags = tags != null ? tags : \"\";\n            if (!tags.contains(tag)) {\n                if (tags.length() > 0) {\n                    tags += \",\" + tag;\n                } else {\n                    tags = tag;\n                }\n            }\n            ContentValues cv = new ContentValues();\n            cv.put(\"tags\", tags);\n            db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgId});\n        }\n        cursor.close();\n        return true;\n    }\n\n    public boolean removeMessageTag(long msgId, String tag) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"tags\"},\n                \"id = ?\", new String[]{\"\" + msgId}, null,null,null);\n        if (cursor.moveToNext()) {\n            String tags = cursor.getString(cursor.getColumnIndex(\"tags\"));\n            tags = tags != null ? tags : \"\";\n            if (tags.contains(tag)) {\n                StringBuilder builder = new StringBuilder();\n                String[] ts = tags.split(\",\");\n                for (int i = 0; i < ts.length; i++) {\n                    if (ts[i].equals(tag)) {\n                        continue;\n                    }\n                    if (builder.length() > 0) {\n                        builder.append(\",\");\n                    }\n                    builder.append(ts[i]);\n                }\n                tags = builder.toString();\n            }\n            ContentValues cv = new ContentValues();\n            cv.put(\"tags\", tags);\n            db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgId});\n        }\n        cursor.close();\n        return true;\n    }\n\n\n    public boolean addMessageReader(long msgId, long uid) {\n        db.beginTransaction();\n\n        try {\n            ContentValues values = new ContentValues();\n            values.put(\"msg_id\", msgId);\n            values.put(\"uid\", uid);\n            long id = db.insert(READED_TABLE_NAME, null, values);\n            if (id == -1) {\n                return false;\n            }\n\n            String sql = \"SELECT COUNT(*) as count FROM group_message_readed WHERE msg_id=?\";\n            Cursor cursor = db.rawQuery(sql, new String[]{\"\"+msgId});\n            boolean r = cursor.moveToNext();\n            if (!r) {\n                cursor.close();\n                return false;\n            }\n            int count = cursor.getInt(cursor.getColumnIndex(\"count\"));\n            cursor.close();\n\n            ContentValues cv = new ContentValues();\n            cv.put(\"reader_count\", count);\n            db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgId});\n\n            db.setTransactionSuccessful();\n            return true;\n        } finally {\n            db.endTransaction();\n        }\n    }\n\n    public List<Long> getMessageReaders(long msgId) {\n        Cursor cursor = db.query(READED_TABLE_NAME, new String[]{\"uid\"},\n                \"msg_id = ?\", new String[]{\"\" + msgId}, null, null, null);\n        ArrayList<Long> readers = new ArrayList<>();\n        while(cursor.moveToNext()) {\n            long uid = cursor.getLong(cursor.getColumnIndex(\"uid\"));\n            readers.add(uid);\n        }\n        cursor.close();\n        return readers;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/SQLPeerMessageDB.java",
    "content": "package com.beetle.bauhinia.db;\n\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Text;\n\nimport java.util.ArrayList;\n\n/**\n * Created by houxh on 14-7-22.\n */\npublic class SQLPeerMessageDB {\n\n    private class PeerMessageIterator implements MessageIterator{\n        protected Cursor cursor;\n\n        public IMessage next() {\n            if (cursor == null) {\n                return null;\n            }\n            boolean r = cursor.moveToNext();\n            if (!r) {\n                cursor.close();\n                cursor = null;\n                return null;\n            }\n            IMessage msg = getMessage(cursor);\n            return msg;\n        }\n    }\n\n    private class ForwardPeerMessageInterator extends PeerMessageIterator {\n        public ForwardPeerMessageInterator(SQLiteDatabase db, long peer)  {\n\n            this.cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"receiver\", \"timestamp\", \"flags\", \"content\", \"secret\"},\n                    \"peer = ? AND secret= ?\", new String[]{\"\"+peer, \"\"+secret}, null, null, \"id DESC\");\n        }\n\n        public ForwardPeerMessageInterator(SQLiteDatabase db, long peer, long position)  {\n            this.cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"receiver\", \"timestamp\", \"flags\", \"content\", \"secret\"},\n                    \"peer = ? AND secret = ? AND id < ?\", new String[]{\"\"+peer, \"\"+secret, \"\"+position},\n                    null, null, \"id DESC\");\n        }\n    }\n\n    private class BackwardPeerMessageInterator extends PeerMessageIterator {\n        public BackwardPeerMessageInterator(SQLiteDatabase db, long peer, long position)  {\n            this.cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"receiver\", \"timestamp\", \"flags\", \"content\", \"secret\"},\n                    \"peer = ? AND secret=? AND id > ?\", new String[]{\"\"+peer, \"\"+secret, \"\"+position},\n                    null, null, \"id\");\n        }\n    }\n\n    private class MiddlePeerMessageInterator extends PeerMessageIterator {\n        public MiddlePeerMessageInterator(SQLiteDatabase db, long peer, long position)  {\n            this.cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"receiver\", \"timestamp\", \"flags\", \"content\", \"secret\"},\n                    \"peer = ? AND secret=? AND id > ? AND id < ?\",\n                    new String[]{\"\"+peer, \"\"+secret, \"\"+(position - 10), \"\" + (position + 10)},\n                    null, null, \"id DESC\");\n        }\n    }\n\n    public class PeerConversationIterator implements ConversationIterator {\n        private Cursor cursor;\n        public PeerConversationIterator(SQLiteDatabase db) {\n            this.cursor = db.query(TABLE_NAME, new String[]{\"MAX(id) as id\", \"peer\"},\n                    null, null, \"peer\", null, null);\n        }\n\n        public IMessage next() {\n            if (cursor == null) {\n                return null;\n            }\n            boolean r = cursor.moveToNext();\n            if (!r) {\n                cursor.close();\n                cursor = null;\n                return null;\n            }\n            long id = cursor.getLong(cursor.getColumnIndex(\"id\"));\n            IMessage msg = getMessage(id);\n            return msg;\n        }\n    }\n\n    static protected final String TABLE_NAME = \"peer_message\";\n    static protected final String FTS_TABLE_NAME = \"peer_message_fts\";\n\n    protected int secret;\n\n    private SQLiteDatabase db;\n\n    public void setDb(SQLiteDatabase db) {\n        this.db = db;\n    }\n\n    public SQLiteDatabase getDb() {\n        return this.db;\n    }\n\n\n    public boolean insertMessage(IMessage msg, long uid) {\n        int s = msg.secret ? 1 : secret;\n        ContentValues values = new ContentValues();\n        values.put(\"peer\", uid);\n        values.put(\"sender\", msg.sender);\n        values.put(\"receiver\", msg.receiver);\n        values.put(\"secret\", s);\n        values.put(\"timestamp\", msg.timestamp);\n        values.put(\"flags\", msg.flags);\n        if (!TextUtils.isEmpty(msg.getUUID())) {\n            values.put(\"uuid\", msg.getUUID());\n        }\n        values.put(\"content\", msg.content.getRaw());\n        long id = db.insert(TABLE_NAME, null, values);\n        if (id == -1) {\n            return  false;\n        }\n        msg.msgLocalID = id;\n\n        if (msg.content.getType() == MessageContent.MessageType.MESSAGE_TEXT) {\n            Log.i(\"goubuli\", \"inserts text message:\" + msg.content);\n            Text text = (Text)msg.content;\n            insertFTS((int)id, text.text);\n        }\n        return true;\n    }\n\n    public boolean updateContent(long msgLocalID, String content) {\n        ContentValues cv = new ContentValues();\n        cv.put(\"content\", content);\n        int rows = db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        return rows == 1;\n    }\n\n    public int acknowledgeMessage(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_ACK);\n    }\n\n    public int markMessageFailure(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_FAILURE);\n    }\n\n    public int markMessageListened(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_LISTENED);\n    }\n\n    public int markMessageReaded(long msgLocalID) {\n        return addFlag(msgLocalID,  MessageFlag.MESSAGE_FLAG_READED);\n    }\n\n    public boolean eraseMessageFailure(long msgLocalID) {\n        int f = MessageFlag.MESSAGE_FLAG_FAILURE;\n        return removeFlag(msgLocalID, f);\n    }\n\n    public int addFlag(long msgLocalID, int f) {\n        int r = 0;\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"flags\"},\n                \"id=?\", new String[]{\"\"+msgLocalID},\n                null, null, null);\n        if (cursor.moveToNext()) {\n            int flags = cursor.getInt(cursor.getColumnIndex(\"flags\"));\n            if ((flags & f) == 0) {\n                flags |= f;\n\n                ContentValues cv = new ContentValues();\n                cv.put(\"flags\", flags);\n                r = db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\" + msgLocalID});\n            }\n        }\n        cursor.close();\n        return r;\n    }\n\n    public boolean removeFlag(long msgLocalID, int f) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"flags\"},\n                \"id=?\", new String[]{\"\"+msgLocalID},\n                null, null, null);\n        if (cursor.moveToNext()) {\n            int flags = cursor.getInt(cursor.getColumnIndex(\"flags\"));\n            flags &= ~f;\n\n            ContentValues cv = new ContentValues();\n            cv.put(\"flags\", flags);\n            db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        }\n        cursor.close();\n        return true;\n    }\n\n    public boolean updateFlag(long msgLocalID, int flags) {\n        ContentValues cv = new ContentValues();\n        cv.put(\"flags\", flags);\n        db.update(TABLE_NAME, cv, \"id = ?\", new String[]{\"\"+msgLocalID});\n        return true;\n    }\n\n\n    public boolean removeMessage(long msgLocalID) {\n        db.delete(TABLE_NAME, \"id = ?\", new String[]{\"\"+msgLocalID});\n        db.delete(FTS_TABLE_NAME, \"rowid = ?\", new String[]{\"\"+msgLocalID});\n        return true;\n    }\n\n    public boolean removeMessageIndex(long msgLocalID) {\n        db.delete(FTS_TABLE_NAME, \"rowid = ?\", new String[]{\"\"+msgLocalID});\n        return true;\n    }\n\n    public boolean clearConversation(long uid) {\n        db.delete(TABLE_NAME, \"peer = ? AND secret= ?\", new String[]{\"\"+uid, \"\" + secret});\n        return true;\n    }\n\n    //获取最近的消息\n    public MessageIterator newMessageIterator(long uid) {\n        return new ForwardPeerMessageInterator(db, uid);\n    }\n\n    //获取之前的消息\n    public MessageIterator newForwardMessageIterator(long uid, long firstMsgID) {\n        return new ForwardPeerMessageInterator(db, uid, firstMsgID);\n    }\n\n    //获取之后的消息\n    public MessageIterator newBackwardMessageIterator(long uid, long msgID) {\n        return new BackwardPeerMessageInterator(db, uid, msgID);\n    }\n\n    //获取前后的消息\n    public MessageIterator newMiddleMessageIterator(long uid, long msgID) {\n        return new MiddlePeerMessageInterator(db, uid, msgID);\n    }\n\n    public ConversationIterator newConversationIterator() {\n        return new PeerConversationIterator(db);\n    }\n\n    private String tokenizer(String key) {\n        StringBuilder builder = new StringBuilder();\n\n        for (int i = 0; i < key.length(); i++) {\n            char c = key.charAt(i);\n            builder.append(c);\n            if (c >= 0x4e00 && c <= 0x9fff) {\n                builder.append(' ');\n            }\n        }\n        return builder.toString();\n    }\n\n    public ArrayList<IMessage> search(String key) {\n        key = key.replace(\"'\", \"\\'\");\n        String k = this.tokenizer(key);\n        Cursor cursor = db.query(FTS_TABLE_NAME, new String[]{\"rowid\"},\n                \"content MATCH(?)\",  new String[]{k},\n                null, null, null);\n\n        ArrayList<Long> rows = new ArrayList<Long>();\n        while(cursor.moveToNext()) {\n            long rowid = cursor.getInt(cursor.getColumnIndex(\"rowid\"));\n            rows.add(rowid);\n        }\n        cursor.close();\n\n        ArrayList<IMessage> messages = new ArrayList<IMessage>();\n        for (int i = 0; i < rows.size(); i++) {\n            IMessage msg = getMessage(rows.get(i));\n            if (msg != null) {\n                messages.add(msg);\n            }\n        }\n        return messages;\n    }\n\n    private boolean insertFTS(int msgLocalID, String text) {\n        String t = tokenizer(text);\n        ContentValues values = new ContentValues();\n        values.put(\"docid\", msgLocalID);\n        values.put(\"content\", t);\n        db.insert(FTS_TABLE_NAME, null, values);\n        return true;\n    }\n\n    private IMessage getMessage(Cursor cursor) {\n        IMessage msg = new IMessage();\n        msg.msgLocalID = cursor.getLong(cursor.getColumnIndex(\"id\"));\n        msg.sender = cursor.getLong(cursor.getColumnIndex(\"sender\"));\n        msg.receiver = cursor.getLong(cursor.getColumnIndex(\"receiver\"));\n        msg.timestamp = cursor.getInt(cursor.getColumnIndex(\"timestamp\"));\n        msg.flags = cursor.getInt(cursor.getColumnIndex(\"flags\"));\n        String content = cursor.getString(cursor.getColumnIndex(\"content\"));\n        msg.secret = cursor.getInt(cursor.getColumnIndex(\"secret\")) == 1;\n        msg.setContent(content);\n        return msg;\n    }\n\n    public IMessage getMessage(long id) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"receiver\", \"timestamp\", \"flags\", \"content\", \"secret\"},\n                \"id = ?\", new String[]{\"\"+id}, null, null, null);\n\n        IMessage msg = null;\n        if (cursor.moveToNext()) {\n            msg = getMessage(cursor);\n        }\n        cursor.close();\n        return msg;\n    }\n\n    public IMessage getMessage(String uuid) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"receiver\", \"timestamp\", \"flags\", \"content\", \"secret\"},\n                \"uuid = ?\", new String[]{uuid}, null, null, null);\n\n        IMessage msg = null;\n        if (cursor.moveToNext()) {\n            msg = getMessage(cursor);\n        }\n        cursor.close();\n        return msg;\n    }\n\n    //获取到最新的消息\n    public IMessage getLastMessage(long peer) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\", \"sender\", \"receiver\", \"timestamp\", \"flags\", \"content\", \"secret\"},\n                \"peer = ? AND secret= ?\", new String[]{\"\"+peer, \"\"+secret}, null, null, \"id DESC\");\n        boolean r = cursor.moveToNext();\n        if (!r) {\n            cursor.close();\n            return null;\n        }\n\n        IMessage msg = getMessage(cursor);\n        cursor.close();\n        return msg;\n    }\n\n    public long getMessageId(String uuid) {\n        Cursor cursor = db.query(TABLE_NAME, new String[]{\"id\"},\n                \"uuid = ?\", new String[]{uuid}, null,null,null);\n        boolean r = cursor.moveToNext();\n        if (!r) {\n            cursor.close();\n            return 0;\n        }\n\n        long msgLocalId = cursor.getLong(cursor.getColumnIndex(\"id\"));\n        cursor.close();\n        return msgLocalId;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/ACK.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\n\npublic class ACK extends Notification {\n    public int error;\n    public MessageType getType() {\n        return MessageType.MESSAGE_ACK;\n    }\n\n\n    public static ACK newACK(int error) {\n        ACK ack = new ACK();\n        JsonObject content = new JsonObject();\n        JsonObject json = new JsonObject();\n        json.addProperty(\"error\", error);\n        content.add(ACK, json);\n        ack.raw = content.toString();\n        ack.error = error;\n        return ack;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Attachment.java",
    "content": "package com.beetle.bauhinia.db.message;\n\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.google.gson.JsonObject;\n\npublic  class Attachment extends MessageContent {\n    public int msg_id;\n    public String address;\n    public String url;\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_ATTACHMENT;\n    }\n\n\n\n    public static Attachment newAttachment(int msgLocalID, String address) {\n        Attachment attachment = new Attachment();\n        JsonObject content = new JsonObject();\n        JsonObject attachmentJson = new JsonObject();\n        attachmentJson.addProperty(\"msg_id\", msgLocalID);\n        attachmentJson.addProperty(\"address\", address);\n        content.add(ATTACHMENT, attachmentJson);\n        attachment.raw = content.toString();\n        attachment.address = address;\n        attachment.msg_id = msgLocalID;\n        return attachment;\n    }\n\n    public static Attachment newURLAttachment(int msgLocalID, String url) {\n        Attachment attachment = new Attachment();\n        JsonObject content = new JsonObject();\n        JsonObject attachmentJson = new JsonObject();\n        attachmentJson.addProperty(\"msg_id\", msgLocalID);\n        attachmentJson.addProperty(\"url\", url);\n        content.add(ATTACHMENT, attachmentJson);\n        attachment.raw = content.toString();\n        attachment.url = url;\n        attachment.msg_id = msgLocalID;\n        return attachment;\n    }\n}"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Audio.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.UUID;\n\npublic  class Audio extends MessageContent {\n    public String url;\n    public long duration;\n\n    Audio(String url, long duration, String uuid) {\n        JsonObject content = new JsonObject();\n        JsonObject audioJson = new JsonObject();\n        audioJson.addProperty(\"duration\", duration);\n        audioJson.addProperty(\"url\", url);\n        content.add(AUDIO, audioJson);\n        content.addProperty(\"uuid\", uuid);\n        this.raw = content.toString();\n        this.duration = duration;\n        this.url = url;\n        this.uuid = uuid;\n    }\n\n\n    public Audio(Audio other, String url) {\n        super(other);\n        this.duration = other.duration;\n        this.url = url;\n        try {\n            JSONObject obj = new JSONObject(raw);\n            obj.getJSONObject(AUDIO).put(\"url\", url);\n            raw = obj.toString();\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private static Audio newAudio(String url, long duration, String uuid) {\n        Audio audio = new Audio(url, duration, uuid);\n        return audio;\n    }\n\n    public static Audio newAudio(String url, long duration) {\n        String uuid = UUID.randomUUID().toString();\n        return newAudio(url, duration, uuid);\n    }\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_AUDIO;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Classroom.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class Classroom extends MessageContent {\n    @SerializedName(\"master_id\")\n    public long masterID;\n\n    @SerializedName(\"channel_id\")\n    public String channelID;\n\n    @SerializedName(\"server_id\")\n    public long serverID;\n\n    @SerializedName(\"mic_mode\")\n    public String micMode;\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_CLASSROOM;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Conference.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class Conference extends MessageContent {\n    @SerializedName(\"master_id\")\n    public long masterID;\n\n    @SerializedName(\"channel_id\")\n    public String channelID;\n\n    @SerializedName(\"server_id\")\n    public long serverID;\n\n    @SerializedName(\"mic_mode\")\n    public String micMode;\n\n    public MessageContent.MessageType getType() {\n        return MessageContent.MessageType.MESSAGE_CONFERENCE;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/File.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.UUID;\n\npublic class File extends MessageContent {\n    public String url;\n    public String filename;\n    public int size;\n\n    public File(String url, String filename, int size) {\n        String uuid = UUID.randomUUID().toString();\n        JsonObject content = new JsonObject();\n        JsonObject obj = new JsonObject();\n        obj.addProperty(\"url\", url);\n        obj.addProperty(\"size\", size);\n        obj.addProperty(\"filename\", filename);\n        content.add(FILE, obj);\n        content.addProperty(\"uuid\", uuid);\n\n        this.raw = content.toString();\n        this.url = url;\n        this.filename = filename;\n        this.size = size;\n        this.uuid = uuid;\n    }\n\n    public File(File other, String url) {\n        super(other);\n        this.filename = other.filename;\n        this.size = other.size;\n        this.url = url;\n        try {\n            JSONObject obj = new JSONObject(raw);\n            obj.getJSONObject(FILE).put(\"url\", url);\n            raw = obj.toString();\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_FILE;\n    }\n\n    public static File newFile(String url, String filename, int size) {\n        return new File(url, filename, size);\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/GroupNotification.java",
    "content": "package com.beetle.bauhinia.db.message;\n\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.google.gson.*;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\n\npublic  class GroupNotification extends Notification {\n    public static final int NOTIFICATION_GROUP_CREATED = 1;//群创建\n    public static final int NOTIFICATION_GROUP_DISBAND = 2;//群解散\n    public static final int NOTIFICATION_GROUP_MEMBER_ADDED = 3;//群成员加入\n    public static final int NOTIFICATION_GROUP_MEMBER_LEAVED = 4;//群成员离开\n    public static final int NOTIFICATION_GROUP_NAME_UPDATED = 5;\n    public static final int NOTIFICATION_GROUP_NOTICE_UPDATED = 6; //群公告\n\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_GROUP_NOTIFICATION;\n    }\n\n    public int notificationType;\n\n    public long groupID;\n\n    public int timestamp;//单位:秒\n\n    //NOTIFICATION_GROUP_CREATED\n    public String groupName;\n    public long master;\n    public ArrayList<Long> members;\n\n    public String notice;\n\n    //NOTIFICATION_GROUP_MEMBER_LEAVED, NOTIFICATION_GROUP_MEMBER_ADDED\n    public long member;\n    public String memberName;\n\n\n\n\n\n    public static GroupNotification newGroupNotification(String text) {\n        GroupNotification notification = new GroupNotification();\n\n        JsonObject content = new JsonObject();\n        content.addProperty(NOTIFICATION, text);\n        notification.raw = content.toString();\n\n        try {\n            Gson gson = new GsonBuilder().create();\n            JsonObject element = gson.fromJson(text, JsonObject.class);\n\n            if (element.has(\"create\")) {\n                JsonObject obj = element.getAsJsonObject(\"create\");\n                notification.groupID = obj.get(\"group_id\").getAsLong();\n                notification.timestamp = obj.get(\"timestamp\").getAsInt();\n                notification.master = obj.get(\"master\").getAsLong();\n                notification.groupName = obj.get(\"name\").getAsString();\n\n                notification.members = new ArrayList<Long>();\n                JsonArray ary = obj.getAsJsonArray(\"members\");\n                Iterator<JsonElement> iter = ary.iterator();\n                while (iter.hasNext()) {\n                    JsonElement e = iter.next();\n                    if (e.isJsonObject()) {\n                        notification.members.add(e.getAsJsonObject().get(\"uid\").getAsLong());\n                    } else {\n                        notification.members.add(e.getAsLong());\n                    }\n                }\n                notification.notificationType = GroupNotification.NOTIFICATION_GROUP_CREATED;\n            } else if (element.has(\"disband\")) {\n                JsonObject obj = element.getAsJsonObject(\"disband\");\n                notification.groupID = obj.get(\"group_id\").getAsLong();\n                notification.timestamp = obj.get(\"timestamp\").getAsInt();\n                notification.notificationType = GroupNotification.NOTIFICATION_GROUP_DISBAND;\n            } else if (element.has(\"quit_group\")) {\n                JsonObject obj = element.getAsJsonObject(\"quit_group\");\n                notification.groupID = obj.get(\"group_id\").getAsLong();\n                notification.timestamp = obj.get(\"timestamp\").getAsInt();\n                notification.member = obj.get(\"member_id\").getAsLong();\n                if (obj.get(\"name\") != null) {\n                    notification.memberName = obj.get(\"name\").getAsString();\n                }\n                notification.notificationType = GroupNotification.NOTIFICATION_GROUP_MEMBER_LEAVED;\n            } else if (element.has(\"add_member\")) {\n                JsonObject obj = element.getAsJsonObject(\"add_member\");\n                notification.groupID = obj.get(\"group_id\").getAsLong();\n                notification.timestamp = obj.get(\"timestamp\").getAsInt();\n                notification.member = obj.get(\"member_id\").getAsLong();\n                if (obj.get(\"name\") != null) {\n                    notification.memberName = obj.get(\"name\").getAsString();\n                }\n                notification.notificationType = GroupNotification.NOTIFICATION_GROUP_MEMBER_ADDED;\n            } else if (element.has(\"update_name\")) {\n                JsonObject obj = element.getAsJsonObject(\"update_name\");\n                notification.groupID = obj.get(\"group_id\").getAsLong();\n                notification.groupName = obj.get(\"name\").getAsString();\n                notification.timestamp = obj.get(\"timestamp\").getAsInt();\n                notification.notificationType = GroupNotification.NOTIFICATION_GROUP_NAME_UPDATED;\n            } else if (element.has(\"update_notice\")) {\n                JsonObject obj = element.getAsJsonObject(\"update_notice\");\n                notification.groupID = obj.get(\"group_id\").getAsLong();\n                notification.timestamp = obj.get(\"timestamp\").getAsInt();\n                notification.notice = obj.get(\"notice\").getAsString();\n                notification.notificationType = GroupNotification.NOTIFICATION_GROUP_NOTICE_UPDATED;\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return notification;\n    }\n\n\n\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/GroupVOIP.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.google.gson.JsonObject;\n\npublic  class GroupVOIP extends Notification {\n    public long initiator;\n    public boolean finished;\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_GROUP_VOIP;\n    }\n\n\n    public static GroupVOIP newGroupVOIP(long initiator, boolean finished) {\n        GroupVOIP gv = new GroupVOIP();\n        JsonObject content = new JsonObject();\n\n        JsonObject json = new JsonObject();\n        json.addProperty(\"initiator\", initiator);\n        json.addProperty(\"finished\", finished);\n        content.add(GROUP_VOIP, json);\n        gv.raw = content.toString();\n\n        gv.initiator = initiator;\n        gv.finished = finished;\n        return gv;\n    }\n\n\n\n}"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Headline.java",
    "content": "package com.beetle.bauhinia.db.message;\n\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.google.gson.JsonObject;\n\npublic  class Headline extends Notification {\n    public String headline;\n\n    public String getDescription() {\n        return this.headline;\n    }\n    public MessageType getType() {\n        return MessageType.MESSAGE_HEADLINE;\n    }\n\n\n\n    public static Headline newHeadline(String headline) {\n        Headline head = new Headline();\n        JsonObject content = new JsonObject();\n        JsonObject headlineJson = new JsonObject();\n        headlineJson.addProperty(\"headline\", headline);\n        content.add(HEADLINE, headlineJson);\n        head.raw = content.toString();\n        head.headline = headline;\n        head.description = headline;\n        return head;\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Image.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.UUID;\n\npublic class Image extends MessageContent {\n    public String url;\n    public int width;\n    public int height;\n\n    public Image(String url, int width, int height, String uuid) {\n        JsonObject content = new JsonObject();\n        JsonObject obj = new JsonObject();\n        obj.addProperty(\"url\", url);\n        obj.addProperty(\"width\", width);\n        obj.addProperty(\"height\", height);\n        content.add(IMAGE2, obj);\n        content.addProperty(\"uuid\", uuid);\n        this.raw = content.toString();\n        this.url = url;\n        this.width = width;\n        this.height = height;\n        this.uuid = uuid;\n    }\n\n    public Image(Image other, String url) {\n        super(other);\n        this.width = other.width;\n        this.height = other.height;\n        this.url = url;\n\n        try {\n            JSONObject obj = new JSONObject(raw);\n            obj.getJSONObject(IMAGE2).put(\"url\", url);\n            raw = obj.toString();\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_IMAGE;\n    }\n    \n    private static Image newImage(String url, int width, int height, String uuid) {\n        return new Image(url, width, height, uuid);\n    }\n\n    public static Image newImage(String url, int width, int height) {\n        String uuid = UUID.randomUUID().toString();\n        return newImage(url, width, height, uuid);\n    }\n\n}"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Link.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.beetle.bauhinia.db.IMessage;\n\npublic  class Link extends MessageContent {\n    public String title;\n    public String content;\n    public String url;\n    public String image;\n    public MessageType getType() { return MessageType.MESSAGE_LINK; }\n}"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Location.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport android.text.TextUtils;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.google.gson.JsonObject;\n\nimport java.util.UUID;\n\npublic  class Location extends MessageContent {\n    public float latitude;\n    public float longitude;\n    public String address;\n    public MessageType getType() { return MessageType.MESSAGE_LOCATION; }\n\n\n    public static Location newLocation(float latitude, float longitude) {\n        return newLocation(latitude, longitude, \"\");\n    }\n\n    public static Location newLocation(float latitude, float longitude, String address) {\n        Location location = new Location();\n        String uuid = UUID.randomUUID().toString();\n\n        JsonObject content = new JsonObject();\n        JsonObject locationJson = new JsonObject();\n        locationJson.addProperty(\"latitude\", latitude);\n        locationJson.addProperty(\"longitude\", longitude);\n        if (!TextUtils.isEmpty(address)) {\n            locationJson.addProperty(\"address\", address);\n        }\n        content.add(LOCATION, locationJson);\n        content.addProperty(\"uuid\", uuid);\n        location.raw = content.toString();\n        location.longitude = longitude;\n        location.latitude = latitude;\n        location.address = address;\n        location.uuid = uuid;\n        return location;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/MessageContent.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport android.text.TextUtils;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\npublic abstract class MessageContent implements Cloneable {\n    public static final String TEXT = \"text\";\n    public static final String IMAGE = \"image\";\n    public static final String IMAGE2 = \"image2\";\n    public static final String LOCATION = \"location\";\n    public static final String AUDIO = \"audio\";\n    public static final String VIDEO = \"video\";\n    public static final String NOTIFICATION = \"notification\";\n    public static final String LINK = \"link\";\n    public static final String ATTACHMENT = \"attachment\";\n    public static final String HEADLINE = \"headline\";\n    public static final String TIMEBASE = \"timebase\";\n    public static final String VOIP = \"voip\";\n    public static final String GROUP_VOIP = \"group_voip\";\n    public static final String P2P_SESSION = \"p2p_session\";\n    public static final String SECRET = \"secret\";\n    public static final String FILE = \"file\";\n    public static final String REVOKE = \"revoke\";\n    public static final String ACK = \"ack\";\n    public static final String CLASSROOM = \"classroom\";\n    public static final String READED = \"readed\";\n    public static final String TAG = \"tag\";\n    public static final String CONFERENCE = \"conference\";\n\n    public enum MessageType {\n        MESSAGE_UNKNOWN,\n        MESSAGE_TEXT,\n        MESSAGE_AUDIO,\n        MESSAGE_IMAGE,\n        MESSAGE_LOCATION,\n        MESSAGE_VIDEO,\n        MESSAGE_GROUP_NOTIFICATION,\n        MESSAGE_LINK,\n        MESSAGE_ATTACHMENT,\n        MESSAGE_HEADLINE, //成为好友提示\n        MESSAGE_VOIP,\n        MESSAGE_GROUP_VOIP,\n        MESSAGE_FILE,\n        MESSAGE_P2P_SESSION,//建立p2p加密会话的控制消息\n        MESSAGE_SECRET,\n        MESSAGE_REVOKE,//撤回消息\n        MESSAGE_ACK,\n        MESSAGE_CLASSROOM,//群课堂\n        MESSAGE_READED, //消息已读\n        MESSAGE_TAG,//给消息打标签\n        MESSAGE_CONFERENCE,//视频会议\n\n        MESSAGE_TIME_BASE, //虚拟的消息，不会存入磁盘\n    }\n\n    @Override\n    public Object clone() {\n        MessageContent stu = null;\n        try{\n            stu = (MessageContent)super.clone();\n        }catch(CloneNotSupportedException e) {\n            e.printStackTrace();\n        }\n        return stu;\n    }\n\n    protected String raw;\n    protected String uuid;\n    protected long groupId;//群组会话内的私聊和群组会话的已读\n    protected String reference;//引用的消息uuid\n\n    //客服会话\n    protected long storeId;\n    protected String sessionId;\n\n    protected String name;\n    protected String appName;\n    protected String storeName;\n\n\n    public MessageContent() {\n\n    }\n\n    public MessageContent(String uuid) {\n        this.uuid = uuid;\n    }\n\n    public MessageContent(MessageContent other) {\n        this.raw = other.raw;\n        this.uuid = other.uuid;\n        this.groupId = other.groupId;\n        this.reference = other.reference;\n        this.storeId = other.storeId;\n        this.sessionId = other.sessionId;\n        this.name = other.name;\n        this.appName = other.appName;\n        this.storeName = other.storeName;\n    }\n    public MessageType getType() {\n        return MessageType.MESSAGE_UNKNOWN;\n    }\n\n    public String getRaw() {\n        return raw;\n    }\n    public void setRaw(String raw) {\n        this.raw = raw;\n    }\n\n    public String getUUID() {\n        return uuid;\n    }\n\n    public void setUUID(String uuid) {\n        this.uuid = uuid;\n    }\n\n    public long getGroupId() {\n        return groupId;\n    }\n\n    public void setGroupId(long groupId) {\n        this.groupId = groupId;\n    }\n\n\n\n    public long getStoreId() {\n        return storeId;\n    }\n\n    public void setStoreId(long storeId) {\n        this.storeId = storeId;\n    }\n\n    public String getSessionId() {\n        return sessionId;\n    }\n\n    public void setSessionId(String sessionId) {\n        this.sessionId = sessionId;\n    }\n\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setAppName(String appName) {\n        this.appName = appName;\n    }\n\n    public String getAppName() {\n        return appName;\n    }\n\n    public void setStoreName(String storeName) {\n        this.storeName = storeName;\n    }\n\n    public String getStoreName() {\n        return storeName;\n    }\n\n    public String getReference() {\n        return reference;\n    }\n\n    public void setReference(String ref) {\n        this.reference = ref;\n    }\n\n    public void generateRaw(String uuid) {\n        if (uuid == null || raw == null) {\n            return;\n        }\n        try {\n            JSONObject obj = new JSONObject(raw);\n            obj.put(\"uuid\", uuid);\n            this.raw = obj.toString();\n            this.uuid = uuid;\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public void generateRaw(long groupId) {\n        if (raw == null) {\n            return;\n        }\n        try {\n            JSONObject obj = new JSONObject(raw);\n            obj.put(\"group_id\", groupId);\n            this.raw = obj.toString();\n            this.groupId = groupId;\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public void generateRaw(String uuid, String reference, long groupId) {\n        if (raw == null) {\n            return;\n        }\n        try {\n            JSONObject obj = new JSONObject(raw);\n            if (!TextUtils.isEmpty(uuid)) {\n                obj.put(\"uuid\", uuid);\n            }\n            if (!TextUtils.isEmpty(reference)) {\n                obj.put(\"reference\", reference);\n            }\n            if (groupId > 0) {\n                obj.put(\"group_id\", groupId);\n            }\n            this.raw = obj.toString();\n            this.groupId = groupId;\n            this.uuid = uuid;\n            this.reference = reference;\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public void generateRaw(long storeId, String sessionId, String storeName, String name, String appName) {\n        if (raw == null) {\n            return;\n        }\n        try {\n            JSONObject obj = new JSONObject(raw);\n            if (!TextUtils.isEmpty(sessionId)) {\n                obj.put(\"session_id\", reference);\n            }\n            if (storeId > 0) {\n                obj.put(\"store_id\", storeId);\n            }\n            if (!TextUtils.isEmpty(storeName)) {\n                obj.put(\"store_name\", storeName);\n            }\n            if (!TextUtils.isEmpty(name)) {\n                obj.put(\"name\", name);\n            }\n            if (!TextUtils.isEmpty(appName)) {\n                obj.put(\"app_name\", appName);\n            }\n\n            this.raw = obj.toString();\n            this.sessionId = sessionId;\n            this.storeId = storeId;\n            this.storeName = storeName;\n            this.name = name;\n            this.appName = appName;\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Notification.java",
    "content": "package com.beetle.bauhinia.db.message;\n\n\n\npublic  abstract class Notification extends MessageContent {\n    public String description;\n\n    public String getDescription() {\n        return this.description;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/P2PSession.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.UUID;\n\npublic class P2PSession extends MessageContent {\n    @SerializedName(\"device_id\")\n    public String deviceID;\n    @SerializedName(\"channel_id\")\n    public String channelID;\n\n\n\n    public static P2PSession newP2PSession(String channelID, String devicdID) {\n        P2PSession s = new P2PSession();\n        String uuid = UUID.randomUUID().toString();\n\n        JsonObject content = new JsonObject();\n        JsonObject textContent = new JsonObject();\n        textContent.addProperty(\"device_id\", devicdID);\n        textContent.addProperty(\"channel_id\", channelID);\n        content.add(P2P_SESSION, textContent);\n        content.addProperty(\"uuid\", uuid);\n\n        s.raw = content.toString();\n        s.deviceID = devicdID;\n        s.channelID = channelID;\n        return s;\n    }\n\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_P2P_SESSION;\n    }\n\n\n}\n\n\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Readed.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\n\npublic class Readed extends MessageContent {\n    public String msgid;\n\n    public static Readed newReaded(String msgid) {\n        JsonObject content = new JsonObject();\n        JsonObject json = new JsonObject();\n        json.addProperty(\"msgid\", msgid);\n        content.add(READED, json);\n        Readed readed = new Readed();\n        readed.setRaw(content.toString());\n        readed.msgid = msgid;\n        return readed;\n    }\n\n    public MessageContent.MessageType getType() {\n        return MessageType.MESSAGE_READED;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Revoke.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\n\npublic class Revoke extends Notification {\n    public String msgid;\n\n    public static Revoke newRevoke(String msgid) {\n        Revoke revoke = new Revoke();\n        JsonObject content = new JsonObject();\n        JsonObject json = new JsonObject();\n        json.addProperty(\"msgid\", msgid);\n        content.add(REVOKE, json);\n\n        revoke.setRaw(content.toString());\n        revoke.msgid = msgid;\n        return revoke;\n    }\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_REVOKE;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Secret.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\n\npublic class Secret extends MessageContent {\n    public String ciphertext;\n    public int type;\n\n\n    public static Secret newSecret(String ciphertext, int type, String uuid) {\n        Secret s = new Secret();\n\n        JsonObject content = new JsonObject();\n        JsonObject audioJson = new JsonObject();\n        audioJson.addProperty(\"ciphertext\", ciphertext);\n        audioJson.addProperty(\"type\", type);\n        content.add(SECRET, audioJson);\n        content.addProperty(\"uuid\", uuid);\n        s.raw = content.toString();\n\n        s.ciphertext = ciphertext;\n        s.type = type;\n        s.uuid = uuid;\n        return s;\n    }\n\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_SECRET;\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Tag.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\nimport com.google.gson.annotations.SerializedName;\n\n\n\npublic class Tag extends MessageContent {\n    @SerializedName(\"add_tag\")\n    public String addTag;\n    @SerializedName(\"delete_tag\")\n    public String deleteTag;\n\n    public String msgid;\n\n    public static Tag newAddTag(String msgid, String t) {\n        Tag tag = new Tag();\n        JsonObject content = new JsonObject();\n        JsonObject json = new JsonObject();\n        json.addProperty(\"add_tag\", t);\n        json.addProperty(\"msgid\", msgid);\n        content.add(TAG, json);\n\n        tag.setRaw(content.toString());\n        tag.msgid = msgid;\n        tag.addTag = t;\n        return tag;\n    }\n\n    public static Tag newDeleteTag(String msgid, String t) {\n        Tag tag = new Tag();\n        JsonObject content = new JsonObject();\n        JsonObject json = new JsonObject();\n        json.addProperty(\"delete_tag\", t);\n        json.addProperty(\"msgid\", msgid);\n        content.add(TAG, json);\n        tag.setRaw(content.toString());\n        tag.msgid = msgid;\n        tag.deleteTag = t;\n        return tag;\n    }\n\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_TAG;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Text.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport android.text.SpannableString;\nimport android.text.TextUtils;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonObject;\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\nimport java.util.UUID;\n\npublic class Text extends MessageContent {\n    public String text;\n    public SpannableString spanText;//emoticon\n\n    public List<Long> at;\n    @SerializedName(\"at_name\")\n    public List<String> atNames;\n\n    public static Text newText(String text) {\n        Text t = new Text();\n        String uuid = UUID.randomUUID().toString();\n\n        JsonObject textContent = new JsonObject();\n        textContent.addProperty(TEXT, text);\n        textContent.addProperty(\"uuid\", uuid);\n        t.raw = textContent.toString();\n        t.text = text;\n        t.uuid = uuid;\n        return t;\n    }\n\n    public static Text newText(String text, List<Long> at, List<String> atNames) {\n        Text t = new Text();\n        String uuid = UUID.randomUUID().toString();\n\n        JsonArray atArray = new JsonArray();\n        JsonArray atNameArray = new JsonArray();\n        if (at != null && atNames != null && at.size() == atNames.size()) {\n            for (int i = 0; i < at.size(); i++) {\n                atArray.add(at.get(i));\n                atNameArray.add(atNames.get(i));\n            }\n        }\n\n        JsonObject textContent = new JsonObject();\n        textContent.addProperty(TEXT, text);\n        textContent.addProperty(\"uuid\", uuid);\n        if (atArray.size() > 0) {\n            textContent.add(\"at\", atArray);\n            textContent.add(\"at_name\", atNameArray);\n        }\n        t.raw = textContent.toString();\n        t.text = text;\n        t.uuid = uuid;\n        t.at = at;\n        return t;\n    }\n\n    public Text(String text) {\n        String uuid = UUID.randomUUID().toString();\n        JsonObject textContent = new JsonObject();\n        textContent.addProperty(TEXT, text);\n        textContent.addProperty(\"uuid\", uuid);\n        this.raw = textContent.toString();\n        this.text = text;\n        this.uuid = uuid;\n    }\n\n    public Text() {\n\n    }\n\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_TEXT;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/TimeBase.java",
    "content": "package com.beetle.bauhinia.db.message;\n\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.google.gson.JsonObject;\n\npublic  class TimeBase extends Notification {\n    public int timestamp;\n    public MessageType getType() {\n        return MessageType.MESSAGE_TIME_BASE;\n    }\n\n\n    public static TimeBase newTimeBase(int timestamp) {\n        TimeBase tb = new TimeBase();\n        JsonObject content = new JsonObject();\n        JsonObject json = new JsonObject();\n        json.addProperty(\"timestamp\", timestamp);\n        content.add(TIMEBASE, json);\n        tb.raw = content.toString();\n        tb.timestamp = timestamp;\n        return tb;\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Unknown.java",
    "content": "package com.beetle.bauhinia.db.message;\n\npublic  class Unknown extends MessageContent {}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/VOIP.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\n\npublic  class VOIP extends MessageContent {\n\n    public static final int VOIP_FLAG_CANCELED = 1;\n    public static final int VOIP_FLAG_REFUSED = 2;\n    public static final int VOIP_FLAG_ACCEPTED = 3;\n    public static final int VOIP_FLAG_UNRECEIVED = 4;\n\n\n\n    public int flag;\n    public int duration;//秒\n    public boolean videoEnabled;\n\n    public MessageType getType() {\n        return MessageType.MESSAGE_VOIP;\n    }\n\n\n\n    public static VOIP newVOIP(int flag, int duration, boolean videoEnabled) {\n        VOIP v = new VOIP();\n        JsonObject content = new JsonObject();\n\n        JsonObject json = new JsonObject();\n        json.addProperty(\"flag\", flag);\n        json.addProperty(\"duration\", duration);\n        json.addProperty(\"video_enabled\", videoEnabled);\n        content.add(VOIP, json);\n\n        v.raw = content.toString();\n        v.flag = flag;\n        v.duration = duration;\n        v.videoEnabled = videoEnabled;\n\n        return v;\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/db/message/Video.java",
    "content": "package com.beetle.bauhinia.db.message;\n\nimport com.google.gson.JsonObject;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.UUID;\n\npublic class Video  extends MessageContent{\n    public String url;\n    public String thumbnail;\n    public int width;\n    public int height;\n    public int duration;\n\n    public Video(String url, String thumbnail, int width, int height, int duration, String uuid) {\n        JsonObject content = new JsonObject();\n\n        JsonObject obj = new JsonObject();\n        obj.addProperty(\"url\", url);\n        obj.addProperty(\"thumbnail\", thumbnail);\n        obj.addProperty(\"width\", width);\n        obj.addProperty(\"height\", height);\n        obj.addProperty(\"duration\", duration);\n        content.add(VIDEO, obj);\n        content.addProperty(\"uuid\", uuid);\n        this.raw = content.toString();\n        this.url = url;\n        this.thumbnail = thumbnail;\n        this.width = width;\n        this.height = height;\n        this.duration = duration;\n        this.uuid = uuid;\n    }\n\n    public Video(Video other, String url, String thumbnail) {\n        super(other);\n        this.width = other.width;\n        this.height = other.height;\n        this.duration = other.duration;\n        this.url = url;\n        this.thumbnail = thumbnail;\n\n        try {\n            JSONObject obj = new JSONObject(raw);\n            obj.getJSONObject(VIDEO).put(\"url\", url);\n            obj.getJSONObject(VIDEO).put(\"thumbnail\", url);\n            raw = obj.toString();\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public MessageType getType() { return MessageType.MESSAGE_VIDEO; }\n\n    private static Video newVideo(String url, String thumbnail, int width, int height, int duration, String uuid) {\n        return new Video(url, thumbnail, width, height, duration, uuid);\n    }\n\n    public static Video newVideo(String url, String thumbnail, int width, int height, int duration) {\n        String uuid = UUID.randomUUID().toString();\n        return newVideo(url, thumbnail, width, height, duration, uuid);\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/GalleryImage.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\n/**\n * Created by hillwind\n */\npublic class GalleryImage implements Parcelable {\n\n    public String path;\n\n    public GalleryImage(String path) {\n        this.path = path;\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeString(this.path);\n    }\n\n    protected GalleryImage(Parcel in) {\n        this.path = in.readString();\n    }\n\n    public static final Creator<GalleryImage> CREATOR = new Creator<GalleryImage>() {\n        @Override\n        public GalleryImage createFromParcel(Parcel source) {\n            return new GalleryImage(source);\n        }\n\n        @Override\n        public GalleryImage[] newArray(int size) {\n            return new GalleryImage[size];\n        }\n    };\n\n    @Override\n    public String toString() {\n        return \"Image{\" +\n                \"path='\" + path + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/tool/Closeables.java",
    "content": "/*\n * Copyright (C) 2007 The Guava Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.beetle.bauhinia.gallery.tool;\n\nimport androidx.annotation.Nullable;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.Reader;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n * Utility methods for working with {@link Closeable} objects.\n *\n * @author Michael Lancaster\n * @since 1.0\n */\n//@Beta\npublic final class Closeables {\n   static final Logger logger = Logger.getLogger(Closeables.class.getName());\n\n  private Closeables() {}\n\n  /**\n   * Closes a {@link Closeable}, with control over whether an {@code IOException} may be thrown.\n   * This is primarily useful in a finally block, where a thrown exception needs to be logged but\n   * not propagated (otherwise the original exception will be lost).\n   *\n   * <p>If {@code swallowIOException} is true then we never throw {@code IOException} but merely log\n   * it.\n   *\n   * <p>Example: <pre>   {@code\n   *\n   *   public void useStreamNicely() throws IOException {\n   *     SomeStream stream = new SomeStream(\"foo\");\n   *     boolean threw = true;\n   *     try {\n   *       // ... code which does something with the stream ...\n   *       threw = false;\n   *     } finally {\n   *       // If an exception occurs, rethrow it only if threw==false:\n   *       Closeables.close(stream, threw);\n   *     }\n   *   }}</pre>\n   *\n   * @param closeable the {@code Closeable} object to be closed, or null, in which case this method\n   *     does nothing\n   * @param swallowIOException if true, don't propagate IO exceptions thrown by the {@code close}\n   *     methods\n   * @throws IOException if {@code swallowIOException} is false and {@code close} throws an\n   *     {@code IOException}.\n   */\n  public static void close(@Nullable Closeable closeable,\n      boolean swallowIOException) throws IOException {\n    if (closeable == null) {\n      return;\n    }\n    try {\n      closeable.close();\n    } catch (IOException e) {\n      if (swallowIOException) {\n        logger.log(Level.WARNING,\n            \"IOException thrown while closing Closeable.\", e);\n      } else {\n        throw e;\n      }\n    }\n  }\n\n  /**\n   * Closes the given {@link InputStream}, logging any {@code IOException} that's thrown rather\n   * than propagating it.\n   *\n   * <p>While it's not safe in the general case to ignore exceptions that are thrown when closing\n   * an I/O resource, it should generally be safe in the case of a resource that's being used only\n   * for reading, such as an {@code InputStream}. Unlike with writable resources, there's no\n   * chance that a failure that occurs when closing the stream indicates a meaningful problem such\n   * as a failure to flush all bytes to the underlying resource.\n   *\n   * @param inputStream the input stream to be closed, or {@code null} in which case this method\n   *     does nothing\n   * @since 17.0\n   */\n  public static void closeQuietly(@Nullable InputStream inputStream) {\n    try {\n      close(inputStream, true);\n    } catch (IOException impossible) {\n      throw new AssertionError(impossible);\n    }\n  }\n\n  /**\n   * Closes the given {@link Reader}, logging any {@code IOException} that's thrown rather than\n   * propagating it.\n   *\n   * <p>While it's not safe in the general case to ignore exceptions that are thrown when closing\n   * an I/O resource, it should generally be safe in the case of a resource that's being used only\n   * for reading, such as a {@code Reader}. Unlike with writable resources, there's no chance that\n   * a failure that occurs when closing the reader indicates a meaningful problem such as a failure\n   * to flush all bytes to the underlying resource.\n   *\n   * @param reader the reader to be closed, or {@code null} in which case this method does nothing\n   * @since 17.0\n   */\n  public static void closeQuietly(@Nullable Reader reader) {\n    try {\n      close(reader, true);\n    } catch (IOException impossible) {\n      throw new AssertionError(impossible);\n    }\n  }\n\n  public static void closeQuietly(@Nullable OutputStream outputStream) {\n    try {\n      close(outputStream, true);\n    } catch (IOException impossible) {\n      throw new AssertionError(impossible);\n    }\n  }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/tool/DisplayUtils.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.tool;\n\nimport android.content.Context;\nimport android.util.TypedValue;\n\n/**\n * Created by hillwind\n */\npublic class DisplayUtils {\n\n    public static int getSizeByGivenAbsSize(Context context, int givenAbsSize) {\n        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, givenAbsSize, context.getResources().getDisplayMetrics());\n    }\n\n    public static int getScreenWidth(Context context) {\n        return context.getResources().getDisplayMetrics().widthPixels;\n    }\n\n    public static int getScreenHeight(Context context) {\n        return context.getResources().getDisplayMetrics().heightPixels;\n    }\n\n    public static float getScreenDensity(Context context) {\n        return context.getResources().getDisplayMetrics().density;\n    }\n\n    public static int getScreenDensityDpi(Context context) {\n        return context.getResources().getDisplayMetrics().densityDpi;\n    }\n\n    public static int dp2px(Context context, float dp) {\n        final float scale = context.getResources().getDisplayMetrics().density;\n        return (int) (dp * scale + 0.5f);\n    }\n\n    public static int px2dp(Context context, float px) {\n        final float scale = context.getResources().getDisplayMetrics().density;\n        return (int) (px / scale + 0.5f);\n    }\n\n    public static int px2sp(Context context, float pxValue) {\n        return (int) (pxValue / context.getResources().getDisplayMetrics().scaledDensity + 0.5f);\n    }\n\n    public static int sp2px(Context context, float spValue) {\n        return (int) (spValue * context.getResources().getDisplayMetrics().scaledDensity + 0.5f);\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/tool/FileUtils.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.tool;\n\nimport java.io.File;\n\n/**\n * Created by hillwind\n */\npublic class FileUtils {\n\n    public static void mkdirIfNeed(File file) {\n        if (file != null && !file.exists()) {\n            mkdirIfNeed(file.getParentFile());\n            file.mkdir();\n        }\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/tool/ImageUtils.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.tool;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.text.TextUtils;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\n/**\n * Created by hillwind\n */\npublic class ImageUtils {\n\n    public static String savePNGImage(Context context, String srcPath, Bitmap bitmap) throws IOException {\n        if (TextUtils.isEmpty(srcPath) || bitmap == null) {\n            return null;\n        }\n        String fileName = Md5FileNameUtils.getMd5FileName(srcPath);\n        return savePNGImageWithFileName(context, fileName, bitmap);\n    }\n\n    public static String savePNGImageWithFileName(Context context, String fileName, Bitmap bitmap) throws IOException {\n        if (TextUtils.isEmpty(fileName) || bitmap == null) {\n            return null;\n        }\n\n        String filePath = null;\n        FileOutputStream fileOutputStream = null;\n        try {\n            File imgFile = new File(StorageUtils.getAlbumDir(context), fileName + \".png\");\n            fileOutputStream = new FileOutputStream(imgFile);\n            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);\n            fileOutputStream.flush();\n            updateMediaStore(context, imgFile);\n            filePath = imgFile.getAbsolutePath();\n        } finally {\n            Closeables.closeQuietly(fileOutputStream);\n        }\n\n        return filePath;\n    }\n\n    public static void updateMediaStore(Context context, File savedFile) {\n        Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);\n        Uri contentUri = Uri.fromFile(savedFile);\n        mediaScanIntent.setData(contentUri);\n        context.sendBroadcast(mediaScanIntent);\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/tool/Md5FileNameUtils.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.tool;\n\n\nimport java.math.BigInteger;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\n/**\n * Created by hillwind\n */\npublic class Md5FileNameUtils {\n\n    private static final String HASH_ALGORITHM = \"MD5\";\n    private static final int RADIX = 10 + 26; // 10 digits + 26 letters\n\n    public static String getMd5FileName(String url) {\n        byte[] md5 = getMD5(url.getBytes());\n        BigInteger bi = new BigInteger(md5).abs();\n        return bi.toString(RADIX);\n    }\n\n    private static byte[] getMD5(byte[] data) {\n        byte[] hash = null;\n        try {\n            MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);\n            digest.update(data);\n            hash = digest.digest();\n        } catch (NoSuchAlgorithmException e) {\n            e.printStackTrace();\n        }\n        return hash;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/tool/StorageUtils.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.tool;\n\nimport android.content.Context;\nimport android.os.Environment;\n\nimport java.io.File;\n\n/**\n * @author hillwind\n */\npublic class StorageUtils {\n\n    public static final String ALBUM_NAME = \"ChatAlbum\";\n\n    public static String getAlbumDir(Context context) {\n        String dir = getAppDir(context) + File.separator + ALBUM_NAME;\n        File dirFile = new File(dir);\n        if (!dirFile.exists()) {\n            FileUtils.mkdirIfNeed(dirFile);\n        }\n        return dir;\n    }\n\n    public static String getAppDir(Context context) {\n        String dir = getCacheDir(context) + File.separator + context.getPackageName();\n        File dirFile = new File(dir);\n        if (!dirFile.exists()) {\n            FileUtils.mkdirIfNeed(dirFile);\n        }\n        return dir;\n    }\n\n    public static String getCacheDir(Context context) {\n        String cachePath;\n        if (isSDCardAvailable()) {\n            cachePath = Environment.getExternalStorageDirectory().getAbsolutePath();\n        } else {\n            cachePath = context.getCacheDir().getPath();\n        }\n\n        return cachePath;\n    }\n\n    public static boolean isSDCardAvailable() {\n        String sdStatus = Environment.getExternalStorageState();\n        return Environment.MEDIA_MOUNTED.equals(sdStatus);\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/ui/GalleryAdapter.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.ui;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.viewpager.widget.PagerAdapter;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewGroup.LayoutParams;\nimport android.widget.Toast;\n\nimport com.beetle.bauhinia.gallery.GalleryImage;\nimport com.beetle.bauhinia.gallery.tool.ImageUtils;\nimport com.beetle.imlib.R;\nimport com.squareup.picasso.Picasso;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport rx.Observable;\nimport rx.Subscriber;\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.schedulers.Schedulers;\nimport uk.co.senab.photoview.PhotoView;\nimport uk.co.senab.photoview.PhotoViewAttacher;\n\n/**\n * Created by hillwind\n */\npublic class GalleryAdapter extends PagerAdapter {\n\n    private OnItemClickListener listener;\n    private final Context context;\n\n    private List<GalleryImage> mPhotos = new ArrayList<GalleryImage>();\n\n    public GalleryAdapter(Context context, List<GalleryImage> photos) {\n        this.context = context;\n        if (photos != null) {\n            mPhotos = photos;\n        }\n    }\n\n    @Override\n    public int getCount() {\n        return mPhotos.size();\n    }\n\n    @Override\n    public boolean isViewFromObject(View view, Object object) {\n        return view == object;\n    }\n\n    @Override\n    public View instantiateItem(final ViewGroup container, final int position) {\n        final PhotoView photoView = new PhotoView(container.getContext());\n\n        final String path = mPhotos.get(position).path;\n        if (path.contains(\":/\")) {\n            Picasso.get().load(path).into(photoView);\n        } else {\n            Picasso.get().load(new File(path)).into(photoView);\n        }\n        container.addView(photoView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);\n        photoView.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (listener != null) {\n                    listener.onItemClick(container, v, position);\n                }\n            }\n        });\n        photoView.setOnViewTapListener(new PhotoViewAttacher.OnViewTapListener() {\n            @Override\n            public void onViewTap(View view, float x, float y) {\n                if (listener != null) {\n                    listener.onItemClick(container, view, position);\n                }\n            }\n        });\n        photoView.setOnLongClickListener(new View.OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                Dialog dialog = PhotoActionPopup.createDialog(context, new PhotoActionPopup.Listener() {\n                    @Override\n                    public void onSaveToPhone() {\n                        saveImageToPhone(photoView, path);\n                    }\n                });\n                dialog.show();\n                return false;\n            }\n        });\n        return photoView;\n    }\n\n    private void saveImageToPhone(final PhotoView photoView, final String path) {\n        Observable.create(new Observable.OnSubscribe<String>() {\n            @Override\n            public void call(Subscriber<? super String> subscriber) {\n                Bitmap bitmap = ((BitmapDrawable) photoView.getDrawable()).getBitmap();\n                try {\n                    String desImagePath = ImageUtils.savePNGImage(context, path, bitmap);\n                    subscriber.onNext(desImagePath);\n                    subscriber.onCompleted();\n                } catch (IOException e) {\n                    subscriber.onError(e);\n                }\n            }\n        })\n                .subscribeOn(Schedulers.io())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Subscriber<String>() {\n                    @Override\n                    public void onCompleted() {\n\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n                        e.printStackTrace();\n                        Toast.makeText(context, R.string.gallery_image_save_failed, Toast.LENGTH_SHORT).show();\n                    }\n\n                    @Override\n                    public void onNext(String s) {\n                        Toast.makeText(context, context.getString(R.string.gallery_image_saved_to) + s, Toast.LENGTH_SHORT).show();\n                    }\n                });\n    }\n\n    @Override\n    public void destroyItem(ViewGroup container, int position, Object object) {\n        container.removeView((View) object);\n    }\n\n    public void setOnItemClickListener(OnItemClickListener listener) {\n        this.listener = listener;\n    }\n\n    public interface OnItemClickListener {\n        void onItemClick(ViewGroup container, View view, int position);\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/ui/GalleryGridAdapter.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.ui;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AbsListView;\nimport android.widget.BaseAdapter;\nimport android.widget.ImageView;\n\nimport com.beetle.bauhinia.gallery.GalleryImage;\nimport com.beetle.imlib.R;\nimport com.squareup.picasso.Picasso;\n\nimport java.io.File;\nimport java.util.List;\n\n/**\n * Created by hillwind\n */\npublic class GalleryGridAdapter extends BaseAdapter {\n\n    private final Context context;\n    private final int screenWidth;\n\n    private final List<GalleryImage> imagesList;\n\n    public GalleryGridAdapter(Context context, List<GalleryImage> imagesList, int screenWidth) {\n        this.context = context;\n        this.imagesList = imagesList;\n        this.screenWidth = screenWidth;\n    }\n\n    @Override\n    public int getCount() {\n        return imagesList == null ? 0 : imagesList.size();\n    }\n\n    @Override\n    public GalleryImage getItem(int position) {\n        return imagesList.get(position);\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return position;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        final GalleryImage ib = getItem(position);\n        final ViewHolder viewHolder;\n        if (convertView == null) {\n            viewHolder = new ViewHolder();\n            convertView = View.inflate(context, R.layout.gallery_activity_gallery_grid_item, null);\n            viewHolder.imageView = (ImageView) convertView.findViewById(R.id.child_image);\n            setConvertViewSize(convertView);\n            convertView.setTag(viewHolder);\n        } else {\n            viewHolder = (ViewHolder) convertView.getTag();\n            viewHolder.imageView.setImageResource(R.drawable.gallery_picture_place_holder);\n        }\n\n        viewHolder.imageView.setTag(ib.path);\n\n        if (ib.path.contains(\":/\")) {\n            Picasso.get().load(ib.path).into(viewHolder.imageView);\n        } else {\n            Picasso.get().load(new File(ib.path)).into(viewHolder.imageView);\n        }\n        return convertView;\n    }\n\n    private void setConvertViewSize(final View convertView) {\n        int height = screenWidth / 3 - 8;\n        convertView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height));\n    }\n\n    public static class ViewHolder {\n        public ImageView imageView;\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/ui/GalleryGridUI.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.ui;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.AdapterView.OnItemClickListener;\nimport android.widget.GridView;\n\nimport com.beetle.bauhinia.activity.BaseActivity;\nimport com.beetle.bauhinia.gallery.GalleryImage;\nimport com.beetle.bauhinia.gallery.tool.DisplayUtils;\nimport com.beetle.imlib.R;\n\nimport java.util.ArrayList;\n\n/**\n * Created by hillwind\n */\npublic class GalleryGridUI extends BaseActivity {\n\n    private static final String INTENT_EXTRA_KEY_IMAGES = \"images\";\n    private static final String INTENT_EXTRA_KEY_POSITION = \"position\";\n\n    private ArrayList<GalleryImage> imagesList;\n\n    private GridView gridView;\n    private GalleryGridAdapter mGridAdapter;\n\n    private int initialPosition = 0;\n\n    public static Intent getCallingIntent(Context context, ArrayList<GalleryImage> imagesList, int position) {\n        Intent intent = new Intent(context, GalleryGridUI.class);\n        intent.putExtra(INTENT_EXTRA_KEY_POSITION, position);\n        intent.putParcelableArrayListExtra(INTENT_EXTRA_KEY_IMAGES, imagesList);\n        return intent;\n    }\n\n    @Override\n    protected void onCreate(Bundle arg0) {\n        super.onCreate(arg0);\n        setContentView(R.layout.gallery_activity_gallery_grid);\n        handleIntent(getIntent());\n        initView();\n    }\n\n    private void handleIntent(Intent intent) {\n        imagesList = intent.getParcelableArrayListExtra(INTENT_EXTRA_KEY_IMAGES);\n        initialPosition = intent.getIntExtra(INTENT_EXTRA_KEY_POSITION, 0);\n    }\n\n    private void initView() {\n        gridView = (GridView) this.findViewById(R.id.child_grid);\n        mGridAdapter = new GalleryGridAdapter(GalleryGridUI.this, imagesList, DisplayUtils.getScreenWidth(this));\n        gridView.setAdapter(mGridAdapter);\n        gridView.setSelection(initialPosition);\n        gridView.setOnItemClickListener(new OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                navigateToViewImageDetail(position);\n            }\n        });\n    }\n\n    private void navigateToViewImageDetail(int position) {\n        Intent intent = GalleryUI.getCallingIntent(this, imagesList, position, true);\n        startActivity(intent);\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/ui/GalleryUI.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.ui;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageButton;\n\nimport com.beetle.bauhinia.activity.BaseActivity;\nimport com.beetle.bauhinia.gallery.GalleryImage;\nimport com.beetle.bauhinia.gallery.view.ScrollViewPager;\nimport com.beetle.imlib.R;\n\nimport java.util.ArrayList;\n\n/**\n * Created by hillwind\n */\npublic class GalleryUI extends BaseActivity {\n\n    private static final String INTENT_EXTRA_KEY_POSITION = \"position\";\n    private static final String INTENT_EXTRA_KEY_IMAGES = \"images\";\n    private static final String INTENT_EXTRA_KEY_IS_ENTER_FROM_GRID = \"is_enter_from_grid\";\n\n    private ScrollViewPager mViewPager;\n    private GalleryAdapter mPagerAdapter;\n    private ImageButton ibViewMorePicture;\n\n    private int mPosition;\n    private int mTotal;\n    private ArrayList<GalleryImage> imagesList;\n    private boolean isEnterFromGrid;\n\n    public static Intent getCallingIntent(Context context, ArrayList<GalleryImage> galleryImages, int position, boolean isEnterFromGrid) {\n        Intent intent = getCallingIntent(context, galleryImages, position);\n        intent.putExtra(INTENT_EXTRA_KEY_IS_ENTER_FROM_GRID, isEnterFromGrid);\n        return intent;\n    }\n\n    public static Intent getCallingIntent(Context context, ArrayList<GalleryImage> galleryImages, int position) {\n        Intent intent = new Intent(context, GalleryUI.class);\n        intent.putParcelableArrayListExtra(INTENT_EXTRA_KEY_IMAGES, galleryImages);\n        intent.putExtra(INTENT_EXTRA_KEY_POSITION, position);\n        return intent;\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.gallery_activity_gallery);\n        initViews();\n        init();\n    }\n\n    private void initViews() {\n        mViewPager = (ScrollViewPager) findViewById(R.id.imagebrowser_svp_pager);\n        ibViewMorePicture = (ImageButton) findViewById(R.id.ib_view_more_picture);\n    }\n\n    private void navigateToViewMorePicture() {\n        Intent intent = GalleryGridUI.getCallingIntent(this, imagesList, mViewPager.getCurrentItem());\n        startActivity(intent);\n    }\n\n    private void init() {\n        mPosition = getIntent().getIntExtra(INTENT_EXTRA_KEY_POSITION, 0);\n        imagesList = getIntent().getParcelableArrayListExtra(INTENT_EXTRA_KEY_IMAGES);\n        isEnterFromGrid = getIntent().getBooleanExtra(INTENT_EXTRA_KEY_IS_ENTER_FROM_GRID, false);\n        mTotal = imagesList.size();\n        if (mPosition > mTotal) {\n            mPosition = mTotal - 1;\n        }\n        mPagerAdapter = new GalleryAdapter(this, imagesList);\n        mPagerAdapter.setOnItemClickListener(new GalleryAdapter.OnItemClickListener() {\n            @Override\n            public void onItemClick(ViewGroup container, View view, int position) {\n                GalleryUI.this.finish();\n            }\n        });\n        mViewPager.setAdapter(mPagerAdapter);\n        if (mTotal > 0) {\n            mViewPager.setCurrentItem(mPosition, false);\n        }\n        if (isEnterFromGrid) {\n            hideViewMorePictureButton();\n        } else {\n            showViewMorePictureButton();\n        }\n    }\n\n    private void hideViewMorePictureButton() {\n        ibViewMorePicture.setVisibility(View.GONE);\n    }\n\n    private void showViewMorePictureButton() {\n        ibViewMorePicture.setVisibility(View.VISIBLE);\n        ibViewMorePicture.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                navigateToViewMorePicture();\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/ui/PhotoActionPopup.java",
    "content": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License\n */\n\npackage com.beetle.bauhinia.gallery.ui;\n\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.ArrayAdapter;\nimport android.widget.ListAdapter;\n\nimport com.beetle.imlib.R;\n\nimport java.util.ArrayList;\n\n/**\n * Shows a popup asking the user what to do for a photo. The result is passed back to the Listener\n */\npublic class PhotoActionPopup {\n    public static final String TAG = PhotoActionPopup.class.getSimpleName();\n\n    public static Dialog createDialog(Context context, final Listener listener) {\n        final ListAdapter adapter = getListAdapter(context, listener);\n\n        final AlertDialog dialog = new AlertDialog.Builder(context)\n                .setAdapter(adapter, null)\n                .create();\n\n        dialog.setCanceledOnTouchOutside(true);\n\n        dialog.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                pickItem(adapter, position);\n                dialog.dismiss();\n            }\n        });\n\n        return dialog;\n    }\n\n    private static void pickItem(ListAdapter adapter, int position) {\n        final ChoiceListItem choice = (ChoiceListItem) adapter.getItem(position);\n        choice.onPick();\n    }\n\n    private static ListAdapter getListAdapter(final Context context, final Listener listener) {\n        final ArrayList<ChoiceListItem> choices = new ArrayList<ChoiceListItem>(1);\n\n        // Save photo.\n        choices.add(new ChoiceListItem(ChoiceListItem.ID_SAVE_TO_PHONE,\n                context.getString(R.string.gallery_save_to_phone),\n                new Runnable() {\n                    @Override\n                    public void run() {\n                        listener.onSaveToPhone();\n                    }\n                }));\n\n        return new ArrayAdapter<ChoiceListItem>(context, R.layout.gallery_select_dialog_item, choices);\n    }\n\n    private static final class ChoiceListItem {\n        private final int mId;\n        private final String mCaption;\n        private final Runnable action;\n\n        public static final int ID_SAVE_TO_PHONE = 0;\n\n        public ChoiceListItem(int id, String caption, Runnable action) {\n            mId = id;\n            mCaption = caption;\n            this.action = action;\n        }\n\n        public void onPick() {\n            if (action != null) {\n                action.run();\n            }\n        }\n\n        @Override\n        public String toString() {\n            return mCaption;\n        }\n\n        public int getId() {\n            return mId;\n        }\n    }\n\n    public interface Listener {\n        void onSaveToPhone();\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/gallery/view/ScrollViewPager.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.gallery.view;\n\nimport android.content.Context;\nimport androidx.viewpager.widget.PagerAdapter;\nimport androidx.viewpager.widget.ViewPager;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\n\npublic class ScrollViewPager extends ViewPager {\n\n    private boolean mIsEnable = true;\n\n    public ScrollViewPager(Context context) {\n        super(context);\n    }\n\n    public ScrollViewPager(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent ev) {\n        if (mIsEnable) {\n            try {\n                return super.onInterceptTouchEvent(ev);\n            } catch (IllegalArgumentException e) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent ev) {\n        if (mIsEnable) {\n            return super.onTouchEvent(ev);\n        }\n        return false;\n    }\n\n    @Override\n    public void setAdapter(PagerAdapter arg0) {\n        super.setAdapter(arg0);\n    }\n\n    public void setAdapter(PagerAdapter arg0, int index) {\n        super.setAdapter(arg0);\n        setCurrentItem(index, false);\n    }\n\n    public void setEnableTouchScroll(boolean isEnable) {\n        mIsEnable = isEnable;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/Contact.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.toolbar;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport androidx.annotation.NonNull;\nimport com.linkedin.android.spyglass.mentions.Mentionable;\n\npublic class Contact implements Mentionable {\n\n    private final String mName;\n    private long uid;\n\n    public Contact(long uid, String name) {\n        this.uid = uid;\n        mName = name;\n    }\n\n    public long getUid() {\n        return uid;\n    }\n    public String getName() {\n        return mName;\n    }\n\n    // --------------------------------------------------\n    // Mentionable Implementation\n    // --------------------------------------------------\n\n    @NonNull\n    @Override\n    public String getTextForDisplayMode(MentionDisplayMode mode) {\n        switch (mode) {\n            case FULL:\n                return \"@\" + mName + \" \";\n            case PARTIAL:\n            case NONE:\n            default:\n                return \"\";\n        }\n    }\n\n    @Override\n    public MentionDeleteStyle getDeleteStyle() {\n        // Note: Cities do not support partial deletion\n        // i.e. \"San Francisco\" -> DEL -> \"\"\n        return MentionDeleteStyle.PARTIAL_NAME_DELETE;\n    }\n\n    @Override\n    public int getSuggestibleId() {\n        return mName.hashCode();\n    }\n\n    @Override\n    public String getSuggestiblePrimaryText() {\n        return mName;\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeLong(uid);\n        dest.writeString(mName);\n    }\n\n    public Contact(Parcel in) {\n        uid = in.readLong();\n        mName = in.readString();\n    }\n\n    public static final Parcelable.Creator<Contact> CREATOR\n            = new Parcelable.Creator<Contact>() {\n        public Contact createFromParcel(Parcel in) {\n            return new Contact(in);\n        }\n\n        public Contact[] newArray(int size) {\n            return new Contact[size];\n        }\n    };\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/EaseChatExtendMenu.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.toolbar;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.util.AttributeSet;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ArrayAdapter;\nimport android.widget.GridView;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.beetle.imlib.R;\n\n/**\n * 按+按钮出来的扩展按钮\n *\n */\npublic class EaseChatExtendMenu extends GridView {\n\n    protected Context context;\n    private List<ChatMenuItemModel> itemModels = new ArrayList<ChatMenuItemModel>();\n\n    public EaseChatExtendMenu(Context context, AttributeSet attrs, int defStyle) {\n        this(context, attrs);\n    }\n\n    public EaseChatExtendMenu(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(context, attrs);\n    }\n\n    public EaseChatExtendMenu(Context context) {\n        super(context);\n        init(context, null);\n    }\n\n    public static int dip2px(Context context, float dipValue){\n        final float scale = context.getResources().getDisplayMetrics().density;\n        return (int)(dipValue * scale + 0.5f);\n    }\n    private void init(Context context, AttributeSet attrs){\n        this.context = context;\n        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.EaseChatExtendMenu);\n        int numColumns = ta.getInt(R.styleable.EaseChatExtendMenu_numColumns, 4);\n        ta.recycle();\n        \n        setNumColumns(numColumns);\n        setStretchMode(GridView.STRETCH_COLUMN_WIDTH);\n        setGravity(Gravity.CENTER_VERTICAL);\n        setVerticalSpacing(dip2px(context, 8));\n    }\n    \n    /**\n     * 初始化\n     */\n    public void init(){\n        setAdapter(new ItemAdapter(context, itemModels));\n    }\n    \n    /**\n     * 注册menu item\n     * \n     * @param name\n     *            item名字\n     * @param drawableRes\n     *            item背景\n     * @param itemId\n     *             id\n     * @param listener\n     *            item点击事件\n     */\n    public void registerMenuItem(String name, int drawableRes, int itemId, EaseChatExtendMenuItemClickListener listener) {\n        ChatMenuItemModel item = new ChatMenuItemModel();\n        item.name = name;\n        item.image = drawableRes;\n        item.id = itemId;\n        item.clickListener = listener;\n        itemModels.add(item);\n    }\n    \n    /**\n     * 注册menu item\n     * \n     * @param nameRes\n     *            item名字的resource id\n     * @param drawableRes\n     *            item背景\n     * @param itemId\n     *             id\n     * @param listener\n     *            item点击事件\n     */\n    public void registerMenuItem(int nameRes, int drawableRes, int itemId, EaseChatExtendMenuItemClickListener listener) {\n        registerMenuItem(context.getString(nameRes), drawableRes, itemId, listener);\n    }\n    \n    \n    private class ItemAdapter extends ArrayAdapter<ChatMenuItemModel>{\n\n        private Context context;\n\n        public ItemAdapter(Context context, List<ChatMenuItemModel> objects) {\n            super(context, 1, objects);\n            this.context = context;\n        }\n        \n        @Override\n        public View getView(final int position, View convertView, ViewGroup parent) {\n            ChatMenuItem menuItem = null;\n            if(convertView == null){\n                convertView = new ChatMenuItem(context);\n            }\n            menuItem = (ChatMenuItem) convertView;\n            menuItem.setImage(getItem(position).image);\n            menuItem.setText(getItem(position).name);\n            menuItem.setOnClickListener(new OnClickListener() {\n                \n                @Override\n                public void onClick(View v) {\n                    if(getItem(position).clickListener != null){\n                        getItem(position).clickListener.onClick(getItem(position).id, v);\n                    }\n                }\n            });\n            return convertView;\n        }\n        \n        \n    }\n    \n    \n    public interface EaseChatExtendMenuItemClickListener{\n        void onClick(int itemId, View view);\n    }\n    \n    \n    class ChatMenuItemModel{\n        String name;\n        int image;\n        int id;\n        EaseChatExtendMenuItemClickListener clickListener;\n    }\n    \n    class ChatMenuItem extends LinearLayout {\n        private ImageView imageView;\n        private TextView textView;\n\n        public ChatMenuItem(Context context, AttributeSet attrs, int defStyle) {\n            this(context, attrs);\n        }\n\n        public ChatMenuItem(Context context, AttributeSet attrs) {\n            super(context, attrs);\n            init(context, attrs);\n        }\n\n        public ChatMenuItem(Context context) {\n            super(context);\n            init(context, null);\n        }\n\n        private void init(Context context, AttributeSet attrs) {\n            LayoutInflater.from(context).inflate(R.layout.ease_chat_menu_item, this);\n            imageView = (ImageView) findViewById(R.id.image);\n            textView = (TextView) findViewById(R.id.text);\n        }\n\n        public void setImage(int resid) {\n            imageView.setBackgroundResource(resid);\n        }\n\n        public void setText(int resid) {\n            textView.setText(resid);\n        }\n\n        public void setText(String text) {\n            textView.setText(text);\n        }\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/EaseChatInputMenu.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.toolbar;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.os.Handler;\nimport androidx.annotation.NonNull;\nimport android.text.Editable;\nimport android.text.SpannableString;\nimport android.text.TextUtils;\nimport android.text.TextWatcher;\nimport android.util.AttributeSet;\nimport android.view.*;\nimport android.widget.EditText;\nimport android.widget.FrameLayout;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\n\nimport com.beetle.imlib.R;\nimport com.beetle.bauhinia.toolbar.EaseChatExtendMenu.EaseChatExtendMenuItemClickListener;\nimport com.beetle.bauhinia.toolbar.emoticon.EmoticonPanel;\nimport com.linkedin.android.spyglass.mentions.MentionSpan;\nimport com.linkedin.android.spyglass.mentions.MentionSpanConfig;\nimport com.linkedin.android.spyglass.tokenization.QueryToken;\nimport com.linkedin.android.spyglass.tokenization.impl.WordTokenizer;\nimport com.linkedin.android.spyglass.tokenization.impl.WordTokenizerConfig;\nimport com.linkedin.android.spyglass.tokenization.interfaces.QueryTokenReceiver;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * 聊天页面底部的聊天输入菜单栏 <br/>\n * 主要包含3个控件:EaseChatPrimaryMenu(主菜单栏，包含文字输入、发送等功能), <br/>\n * EaseChatExtendMenu(扩展栏，点击加号按钮出来的小宫格的菜单栏), <br/>\n * 以及EaseEmojiconMenu(表情栏)\n */\npublic class\nEaseChatInputMenu extends LinearLayout implements View.OnClickListener, QueryTokenReceiver {\n    FrameLayout primaryMenuContainer;\n    protected RelativeLayout emojiconMenuContainer;\n    protected EaseChatPrimaryMenu chatPrimaryMenu;\n    private EmoticonPanel mEmoticonPanel;\n    protected EaseChatExtendMenu chatExtendMenu;\n    protected FrameLayout chatExtendMenuContainer;\n    protected LayoutInflater layoutInflater;\n\n    private EditText mEtSend;\n\n    private Handler handler = new Handler();\n    private ChatInputMenuListener listener;\n\n    public EaseChatInputMenu(Context context, AttributeSet attrs, int defStyle) {\n        this(context, attrs);\n    }\n\n    public EaseChatInputMenu(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(context, attrs);\n    }\n\n    public EaseChatInputMenu(Context context) {\n        super(context);\n        init(context, null);\n    }\n\n    private void init(Context context, AttributeSet attrs) {\n        layoutInflater = LayoutInflater.from(context);\n        layoutInflater.inflate(R.layout.ease_widget_chat_input_menu, this);\n        primaryMenuContainer = (FrameLayout) findViewById(R.id.primary_menu_container);\n        emojiconMenuContainer = (RelativeLayout) findViewById(R.id.menu_container);\n        chatExtendMenuContainer = (FrameLayout) findViewById(R.id.extend_menu_container);\n         // 扩展按钮栏\n        chatExtendMenu = (EaseChatExtendMenu) findViewById(R.id.extend_menu);\n\n        chatPrimaryMenu = (EaseChatPrimaryMenu) primaryMenuContainer.findViewById(R.id.primary_menu);\n        mEtSend = chatPrimaryMenu.editText;\n        mEmoticonPanel = new EmoticonPanel(context);\n        emojiconMenuContainer.addView(mEmoticonPanel);\n\n        chatPrimaryMenu.buttonSend.setOnClickListener(this);\n        chatPrimaryMenu.buttonSetModeKeyboard.setOnClickListener(this);\n        chatPrimaryMenu.buttonSetModeVoice.setOnClickListener(this);\n        chatPrimaryMenu.buttonMore.setOnClickListener(this);\n        chatPrimaryMenu.faceLayout.setOnClickListener(this);\n        chatPrimaryMenu.editText.setOnClickListener(this);\n        chatPrimaryMenu.editText.setOnFocusChangeListener(new OnFocusChangeListener() {\n            @Override\n            public void onFocusChange(View v, boolean hasFocus) {\n                if (hasFocus) {\n                    chatPrimaryMenu.edittext_layout.setBackgroundResource(R.drawable.ease_input_bar_bg_active);\n                } else {\n                    chatPrimaryMenu.edittext_layout.setBackgroundResource(R.drawable.ease_input_bar_bg_normal);\n                }\n\n                if (listener != null) {\n                    listener.onFocusChanged(hasFocus);\n                }\n            }\n        });\n\n        // 监听文字框\n        chatPrimaryMenu.editText.addTextChangedListener(new TextWatcher() {\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n                if (!TextUtils.isEmpty(s)) {\n                    chatPrimaryMenu.buttonMore.setVisibility(View.GONE);\n                    chatPrimaryMenu.buttonSend.setVisibility(View.VISIBLE);\n                } else {\n                    chatPrimaryMenu.buttonMore.setVisibility(View.VISIBLE);\n                    chatPrimaryMenu.buttonSend.setVisibility(View.GONE);\n                }\n            }\n\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n\n            }\n        });\n\n\n\n        WordTokenizerConfig tokenizerConfig = new WordTokenizerConfig.Builder().build();\n        WordTokenizer tokenizer = new WordTokenizer(tokenizerConfig);\n        chatPrimaryMenu.editText.setTokenizer(tokenizer);\n        MentionSpanConfig.Builder configBuilder = new MentionSpanConfig.Builder();\n        configBuilder.setMentionTextColor(Color.BLACK);\n        configBuilder.setMentionTextBackgroundColor(Color.TRANSPARENT);\n        MentionSpanConfig config = configBuilder.build();\n        chatPrimaryMenu.editText.setMentionSpanConfig(config);\n        chatPrimaryMenu.editText.setAvoidPrefixOnTap(true);\n        chatPrimaryMenu.editText.setQueryTokenReceiver(this);\n\n\n        chatPrimaryMenu.buttonPressToSpeak.setOnTouchListener(new OnTouchListener() {\n\n            @Override\n            public boolean onTouch(View v, MotionEvent event) {\n                if(listener != null){\n                    return listener.onPressToSpeakBtnTouch(v, event);\n                }\n                return false;\n            }\n        });\n\n        processEmoticon();\n    }\n\n    /**\n     * init view 此方法需放在registerExtendMenuItem后面及setCustomEmojiconMenu，\n     * setCustomPrimaryMenu(如果需要自定义这两个menu)后面\n     */\n    public void init() {\n        // 初始化extendmenu\n        chatExtendMenu.init();\n    }\n\n    public void disableSend() {\n        hideKeyboard();\n        chatPrimaryMenu.buttonMore.setEnabled(false);\n        chatPrimaryMenu.buttonSend.setEnabled(false);\n        chatPrimaryMenu.buttonPressToSpeak.setEnabled(false);\n        chatPrimaryMenu.buttonSetModeVoice.setEnabled(false);\n        chatPrimaryMenu.buttonSetModeKeyboard.setEnabled(false);\n        chatPrimaryMenu.faceLayout.setEnabled(false);\n        chatPrimaryMenu.editText.setEnabled(false);\n    }\n\n    public void enableSend() {\n        chatPrimaryMenu.buttonMore.setEnabled(true);\n        chatPrimaryMenu.buttonSend.setEnabled(true);\n        chatPrimaryMenu.buttonPressToSpeak.setEnabled(true);\n        chatPrimaryMenu.buttonSetModeVoice.setEnabled(true);\n        chatPrimaryMenu.buttonSetModeKeyboard.setEnabled(true);\n        chatPrimaryMenu.faceLayout.setEnabled(true);\n        chatPrimaryMenu.editText.setEnabled(true);\n    }\n\n    public void clearFocus() {\n        chatPrimaryMenu.editText.clearFocus();\n    }\n\n    public void atUser(long uid, String name) {\n        Contact c = new Contact(uid, name);\n        chatPrimaryMenu.editText.insertMention(c);\n    }\n\n    /**\n     * 点击事件\n     * @param view\n     */\n    @Override\n    public void onClick(View view){\n        int id = view.getId();\n        if (id == R.id.btn_send) {\n            if(listener != null){\n                String ss = chatPrimaryMenu.editText.getText().toString();\n                List<MentionSpan> mentions = chatPrimaryMenu.editText.getMentionsText().getMentionSpans();\n                Set<Long> atSet = new HashSet<>();\n                ArrayList<Long> atList = new ArrayList<>();\n                ArrayList<String> atNames = new ArrayList<>();\n                for(int i = 0; i < mentions.size(); i++) {\n                    Contact c = (Contact)mentions.get(i).getMention();\n                    if (!atSet.contains(c.getUid())) {\n                        atList.add(c.getUid());\n                        atNames.add(c.getName());\n                        atSet.add(c.getUid());\n                    }\n                }\n                chatPrimaryMenu.editText.setText(\"\");\n                listener.onSendMessage(ss, atList, atNames);\n            }\n        } else if (id == R.id.btn_set_mode_voice) {\n            chatPrimaryMenu.setModeVoice();\n            chatPrimaryMenu.showNormalFaceImage();\n            hideExtendMenuContainer();\n        } else if (id == R.id.btn_set_mode_keyboard) {\n            chatPrimaryMenu.setModeKeyboard();\n            chatPrimaryMenu.showNormalFaceImage();\n            hideExtendMenuContainer();\n        } else if (id == R.id.btn_more) {\n            chatPrimaryMenu.buttonSetModeVoice.setVisibility(View.VISIBLE);\n            chatPrimaryMenu.buttonSetModeKeyboard.setVisibility(View.GONE);\n            chatPrimaryMenu.edittext_layout.setVisibility(View.VISIBLE);\n            chatPrimaryMenu.buttonPressToSpeak.setVisibility(View.GONE);\n            chatPrimaryMenu.showNormalFaceImage();\n            toggleMore();\n        } else if (id == R.id.et_sendmessage) {\n            chatPrimaryMenu.edittext_layout.setBackgroundResource(R.drawable.ease_input_bar_bg_active);\n            chatPrimaryMenu.faceNormal.setVisibility(View.VISIBLE);\n            chatPrimaryMenu.faceChecked.setVisibility(View.INVISIBLE);\n            hideExtendMenuContainer();\n        } else if (id == R.id.rl_face) {\n            chatPrimaryMenu.toggleFaceImage();\n            toggleEmojicon();\n        }\n    }\n\n\n    /**\n     * 注册扩展菜单的item\n     * \n     * @param name\n     *            item名字\n     * @param drawableRes\n     *            item背景\n     * @param itemId\n     *            id\n     * @param listener\n     *            item点击事件\n     */\n    public void registerExtendMenuItem(String name, int drawableRes, int itemId,\n            EaseChatExtendMenuItemClickListener listener) {\n        chatExtendMenu.registerMenuItem(name, drawableRes, itemId, listener);\n    }\n\n    /**\n     * 注册扩展菜单的item\n     * \n     * @param nameRes\n     *            item名字\n     * @param drawableRes\n     *            item背景\n     * @param itemId\n     *            id\n     * @param listener\n     *            item点击事件\n     */\n    public void registerExtendMenuItem(int nameRes, int drawableRes, int itemId,\n            EaseChatExtendMenuItemClickListener listener) {\n        chatExtendMenu.registerMenuItem(nameRes, drawableRes, itemId, listener);\n    }\n\n\n\n\n    protected void processEmoticon() {\n        mEmoticonPanel.setOnItemEmoticonClickListener(new EmoticonPanel.OnItemEmoticonClickListener() {\n            @Override\n            public void onEmoticonClick(SpannableString spannableString) {\n                int index = mEtSend.getSelectionStart();\n                Editable editable = mEtSend.getEditableText();\n                editable.insert(index, spannableString);\n                mEtSend.requestFocus();\n            }\n\n            @Override\n            public void onEmoticonDeleted() {\n                if (!TextUtils.isEmpty(mEtSend.getText())) {\n                    KeyEvent event = new KeyEvent(0, 0, 0, KeyEvent.KEYCODE_DEL,\n                            0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL);\n                    mEtSend.dispatchKeyEvent(event);\n                }\n            }\n        });\n    }\n\n    @Override\n    public List<String> onQueryReceived(final @NonNull QueryToken queryToken) {\n        if (listener != null && queryToken.getTokenString().equals(\"@\")) {\n            listener.onAt();\n        }\n        return null;\n    }\n\n    /**\n     * 显示或隐藏图标按钮页\n     * \n     */\n    protected void toggleMore() {\n        if (chatExtendMenuContainer.getVisibility() == View.GONE) {\n            hideKeyboard();\n            handler.postDelayed(new Runnable() {\n                public void run() {\n                    chatExtendMenuContainer.setVisibility(View.VISIBLE);\n                    chatExtendMenu.setVisibility(View.VISIBLE);\n                    emojiconMenuContainer.setVisibility(View.GONE);\n                }\n            }, 50);\n        } else {\n            if (emojiconMenuContainer.getVisibility() == View.VISIBLE) {\n                emojiconMenuContainer.setVisibility(View.GONE);\n                chatExtendMenu.setVisibility(View.VISIBLE);\n            } else {\n                chatExtendMenuContainer.setVisibility(View.GONE);\n                chatPrimaryMenu.showKeyboard();\n            }\n        }\n    }\n\n    /**\n     * 显示或隐藏表情页\n     */\n    protected void toggleEmojicon() {\n        if (chatExtendMenuContainer.getVisibility() == View.GONE) {\n            hideKeyboard();\n            handler.postDelayed(new Runnable() {\n                public void run() {\n                    chatExtendMenuContainer.setVisibility(View.VISIBLE);\n                    chatExtendMenu.setVisibility(View.GONE);\n                    emojiconMenuContainer.setVisibility(View.VISIBLE);\n                }\n            }, 50);\n        } else {\n            if (emojiconMenuContainer.getVisibility() == View.VISIBLE) {\n                chatExtendMenuContainer.setVisibility(View.GONE);\n                emojiconMenuContainer.setVisibility(View.GONE);\n                chatPrimaryMenu.showKeyboard();\n            } else {\n                chatExtendMenu.setVisibility(View.GONE);\n                emojiconMenuContainer.setVisibility(View.VISIBLE);\n            }\n        }\n    }\n\n\n    /**\n     * 隐藏软键盘\n     */\n    public void hideKeyboard() {\n        chatPrimaryMenu.hideKeyboard();\n\n    }\n\n\n    /**\n     * 隐藏整个扩展按钮栏(包括表情栏)\n     */\n    public void hideExtendMenuContainer() {\n        chatExtendMenu.setVisibility(View.GONE);\n        emojiconMenuContainer.setVisibility(View.GONE);\n        chatExtendMenuContainer.setVisibility(View.GONE);\n        chatPrimaryMenu.showNormalFaceImage();\n    }\n\n    /**\n     * 系统返回键被按时调用此方法\n     * \n     * @return 返回false表示返回键时扩展菜单栏时打开状态，true则表示按返回键时扩展栏是关闭状态<br/>\n     *         如果返回时打开状态状态，会先关闭扩展栏再返回值\n     */\n    public boolean onBackPressed() {\n        if (chatExtendMenuContainer.getVisibility() == View.VISIBLE) {\n            hideExtendMenuContainer();\n            return false;\n        } else {\n            return true;\n        }\n    }\n\n    public void setChatInputMenuListener(ChatInputMenuListener listener) {\n        this.listener = listener;\n    }\n\n    public interface ChatInputMenuListener {\n        /**\n         * 发送消息按钮点击\n         * \n         * @param content\n         *            文本内容\n         */\n        void onSendMessage(String content, List<Long> at, List<String> atNames);\n\n        /**\n         * 长按说话按钮touch事件\n         * @param v\n         * @param event\n         * @return\n         */\n        boolean onPressToSpeakBtnTouch(View v, MotionEvent event);\n\n        /**\n         * edittext 焦点变化\n         * @param hasFocus\n         */\n        void onFocusChanged(boolean hasFocus);\n\n        /**\n         * 用户输入@\n         */\n        void onAt();\n\n    }\n    \n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/EaseChatPrimaryMenu.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.toolbar;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.Button;\nimport android.widget.ImageView;\nimport android.widget.RelativeLayout;\nimport com.beetle.imlib.R;\nimport com.linkedin.android.spyglass.ui.MentionsEditText;\n\n/**\n * 聊天输入栏主菜单栏\n *\n */\npublic class EaseChatPrimaryMenu extends RelativeLayout  {\n    public MentionsEditText editText;\n    public View buttonSetModeKeyboard;\n    public RelativeLayout edittext_layout;\n    public View buttonSetModeVoice;\n    public View buttonSend;\n    public View buttonPressToSpeak;\n    public ImageView faceNormal;\n    public ImageView faceChecked;\n    public Button buttonMore;\n    public RelativeLayout faceLayout;\n    public Activity activity;\n    public InputMethodManager inputManager;\n\n\n    public EaseChatPrimaryMenu(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        init(context, attrs);\n    }\n\n    public EaseChatPrimaryMenu(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public EaseChatPrimaryMenu(Context context) {\n        super(context);\n        init(context, null);\n    }\n\n    private void init(final Context context, AttributeSet attrs) {\n        this.activity = (Activity) context;\n        inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);\n\n        LayoutInflater.from(context).inflate(R.layout.ease_widget_chat_primary_menu, this);\n        editText = (MentionsEditText) findViewById(R.id.et_sendmessage);\n        buttonSetModeKeyboard = findViewById(R.id.btn_set_mode_keyboard);\n        edittext_layout = (RelativeLayout) findViewById(R.id.edittext_layout);\n        buttonSetModeVoice = findViewById(R.id.btn_set_mode_voice);\n        buttonSend = findViewById(R.id.btn_send);\n        buttonPressToSpeak = findViewById(R.id.btn_press_to_speak);\n        faceNormal = (ImageView) findViewById(R.id.iv_face_normal);\n        faceChecked = (ImageView) findViewById(R.id.iv_face_checked);\n        faceLayout = (RelativeLayout) findViewById(R.id.rl_face);\n        buttonMore = (Button) findViewById(R.id.btn_more);\n        edittext_layout.setBackgroundResource(R.drawable.ease_input_bar_bg_normal);\n    }\n\n    /**\n     * 显示语音图标按钮\n     * \n     */\n    public void setModeVoice() {\n        hideKeyboard();\n        edittext_layout.setVisibility(View.GONE);\n        buttonSetModeVoice.setVisibility(View.GONE);\n        buttonSetModeKeyboard.setVisibility(View.VISIBLE);\n        buttonSend.setVisibility(View.GONE);\n        buttonMore.setVisibility(View.VISIBLE);\n        buttonPressToSpeak.setVisibility(View.VISIBLE);\n        faceNormal.setVisibility(View.VISIBLE);\n        faceChecked.setVisibility(View.INVISIBLE);\n    }\n\n    /**\n     * 显示键盘图标\n     */\n    public void setModeKeyboard() {\n        edittext_layout.setVisibility(View.VISIBLE);\n        buttonSetModeKeyboard.setVisibility(View.GONE);\n        buttonSetModeVoice.setVisibility(View.VISIBLE);\n        showKeyboard();\n        buttonPressToSpeak.setVisibility(View.GONE);\n        if (TextUtils.isEmpty(editText.getText())) {\n            buttonMore.setVisibility(View.VISIBLE);\n            buttonSend.setVisibility(View.GONE);\n        } else {\n            buttonMore.setVisibility(View.GONE);\n            buttonSend.setVisibility(View.VISIBLE);\n        }\n\n    }\n\n\n    public void toggleFaceImage(){\n        if(faceNormal.getVisibility() == View.VISIBLE){\n            showSelectedFaceImage();\n        }else{\n            showNormalFaceImage();\n        }\n    }\n\n    public void showNormalFaceImage(){\n        faceNormal.setVisibility(View.VISIBLE);\n        faceChecked.setVisibility(View.INVISIBLE);\n    }\n\n    public void showSelectedFaceImage(){\n        faceNormal.setVisibility(View.INVISIBLE);\n        faceChecked.setVisibility(View.VISIBLE);\n    }\n    \n    /**\n     * 隐藏软键盘\n     */\n    public void hideKeyboard() {\n        if (activity.getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) {\n            if (activity.getCurrentFocus() != null)\n                inputManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);\n        }\n    }\n\n    public void showKeyboard() {\n        editText.requestFocus();\n        inputManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/EaseExpandRecylerView.java",
    "content": "/**\n * Copyright (C) 2013-2014 EaseMob Technologies. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *     http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.beetle.bauhinia.toolbar;\n\nimport android.content.Context;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.util.AttributeSet;\nimport android.view.View;\n\npublic class EaseExpandRecylerView extends RecyclerView {\n\n    public EaseExpandRecylerView(Context context) {\n        super(context);\n    }\n\n    public EaseExpandRecylerView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    @Override\n    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int expandSpec = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);\n        super.onMeasure(widthMeasureSpec, expandSpec);\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/Emoticon.java",
    "content": "package com.beetle.bauhinia.toolbar.emoticon;\n\nimport android.graphics.Bitmap;\n\n/**\n * Desc.\n *\n * @author chenxj(陈贤靖)\n * @date 2019/3/11\n */\npublic class Emoticon {\n\n    private int id;\n\n    private String desc;\n\n    private String name;\n\n    private Bitmap bitmap;\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public String getDesc() {\n        return desc;\n    }\n\n    public void setDesc(String desc) {\n        this.desc = desc;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n    public void setBitmap(Bitmap bitmap) {\n        this.bitmap = bitmap;\n    }\n    public Bitmap getBitmap() {\n        return bitmap;\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/EmoticonAdapter.java",
    "content": "package com.beetle.bauhinia.toolbar.emoticon;\n\nimport android.content.Context;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport com.beetle.imlib.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Desc.\n *\n * @author chenxj(陈贤靖)\n * @date 2019/3/11\n */\npublic class EmoticonAdapter extends RecyclerView.Adapter<EmoticonAdapter.BaseViewHolder> {\n\n    private Context mContext;\n\n    private List<Emoticon> mEmoticonList;\n    private OnItemClickListener mOnItemClickListener;\n\n    public EmoticonAdapter(Context context, @Nullable List<Emoticon> data) {\n        mEmoticonList = data;\n        if (mEmoticonList == null) {\n            mEmoticonList = new ArrayList<>();\n        }\n        mContext = context;\n    }\n\n    @Override\n    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        View view = LayoutInflater.from(mContext).inflate(R.layout.item_emoticon, parent, false);\n        return new BaseViewHolder(view);\n    }\n\n    @Override\n    public void onBindViewHolder(BaseViewHolder holder, final int position) {\n        Emoticon emoticon = mEmoticonList.get(position);\n        if (emoticon.getId() == R.drawable.emoji_item_delete) {\n            holder.ivEmoticon.setImageDrawable(mContext.getResources().getDrawable(R.drawable.emoji_item_delete));\n        } else {\n            holder.ivEmoticon.setImageBitmap(emoticon.getBitmap());\n        }\n        holder.itemView.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mOnItemClickListener != null) {\n                    mOnItemClickListener.onClick(position);\n                }\n            }\n        });\n    }\n\n    @Override\n    public int getItemCount() {\n        return mEmoticonList.size();\n    }\n\n    public Emoticon getItem(int position) {\n        return mEmoticonList.get(position);\n    }\n\n    public void setOnClickListener(OnItemClickListener listener) {\n        if (listener != null) {\n            this.mOnItemClickListener = listener;\n        }\n    }\n\n    public interface OnItemClickListener {\n\n        void onClick(int position);\n    }\n\n    class BaseViewHolder extends RecyclerView.ViewHolder {\n\n        public ImageView ivEmoticon;\n\n        public BaseViewHolder(View itemView) {\n            super(itemView);\n            ivEmoticon = (ImageView) itemView.findViewById(R.id.iv_emoticon);\n        }\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/EmoticonManager.java",
    "content": "package com.beetle.bauhinia.toolbar.emoticon;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.text.Spannable;\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.text.TextUtils;\nimport android.text.style.ImageSpan;\n\nimport com.beetle.imlib.R;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static com.beetle.bauhinia.toolbar.emoticon.EmoticonUtils.FILE_EMOTICON;\n\n/**\n * Desc.\n *\n * @author chenxj(陈贤靖)\n * @date 2019/3/11\n */\npublic class EmoticonManager {\n\n    private static final String TAG = \"EmoticonManager\";\n\n    /**\n     * 每一页表情数量\n     */\n    private static final int PAGE_EMOTICON_SIZE = 31;\n    /**\n     * 十六进制数据的正则表达式\n     */\n    private final static String REGEX_HEX = \"[0-9a-fA-F]+\";\n    /**\n     * 微信自定义表情的正则表达式, [text]\n     */\n    private final static String REGEX_CONTAIN_EMOTION = \"\\\\[[^\\\\]]+\\\\]\";\n    /**\n     * emoji表情unicode对应的十六进制的正则表达式\n     */\n    private final static String REGEX_DIVERSE_EMOJI = \"[a-fA-F0-9]{5}\";\n    /**\n     * 表情分页的结果集合\n     */\n    public List<List<Emoticon>> mEmoticonPageList = new ArrayList<>();\n    /**\n     * 保存于内存中的表情HashMap\n     */\n    private HashMap<String, Emoticon> mEmoticons = new HashMap<>();\n\n    /**\n     * 保存于内存中的表情列表\n     */\n    private List<Emoticon> mEmoticonList = new ArrayList<>();\n    private Context mContext;\n\n    private Set<String> mEmojiSet;\n\n    private int mEmoticonSize;\n\n    public static EmoticonManager getInstance() {\n        return InstanceContainer.ISNATNCE;\n    }\n\n\n    /**\n     * 初始化\n     *\n     * @param context\n     */\n    public void init(Context context) {\n        mContext = context;\n        mEmojiSet = EmoticonUtils.getEmojiEncodeSet();\n        mEmoticonSize = EmoticonUtils.getNormalSize(context);\n\n        loadUnicodeEmoji(context);\n        if (false) {\n            //disable image emoji\n            loadImageEmoji(context);\n        }\n\n        int pageCount = (int) Math.ceil((double) mEmoticonList.size() / PAGE_EMOTICON_SIZE);\n        for (int i = 0; i < pageCount; i++) {\n            mEmoticonPageList.add(getPageData(i));\n        }\n    }\n\n    private void loadUnicodeEmoji(Context context) {\n        Map<String, String> emojis = EmoticonUtils.getEmojiMap();\n\n        for (Map.Entry<String, String> e : emojis.entrySet()) {\n            String desc = e.getKey();\n            String fileName = e.getValue();\n            int resId = context.getResources().getIdentifier(fileName, \"drawable\", context.getPackageName());\n            if (resId != 0) {\n                Emoticon emoticon = new Emoticon();\n                emoticon.setId(resId);\n                emoticon.setName(fileName);\n                emoticon.setDesc(desc);\n                Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId);\n                bitmap = Bitmap.createScaledBitmap(bitmap, mEmoticonSize, mEmoticonSize, true);\n                emoticon.setBitmap(bitmap);\n                mEmoticonList.add(emoticon);\n                mEmoticons.put(desc, emoticon);\n            }\n        }\n    }\n\n    private void loadImageEmoji(Context context) {\n        List<String> emoticonStrList = EmoticonUtils.readFile(context, FILE_EMOTICON);\n        //已经加载过数据，或待解析数据集为空，直接返回\n        if (emoticonStrList.size() <= 0) {\n            return;\n        }\n        for (String str : emoticonStrList) {\n            String[] text = str.split(\",\");\n            String fileName = text[0].substring(0, text[0].lastIndexOf(\".\"));\n            String desc = text[1];\n            int resId = context.getResources().getIdentifier(fileName, \"drawable\", context.getPackageName());\n            if (resId != 0) {\n                Emoticon emoticon = new Emoticon();\n                emoticon.setId(resId);\n                emoticon.setName(fileName);\n                emoticon.setDesc(desc);\n                Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId);\n                bitmap = Bitmap.createScaledBitmap(bitmap, mEmoticonSize, mEmoticonSize, true);\n                emoticon.setBitmap(bitmap);\n                mEmoticonList.add(emoticon);\n                mEmoticons.put(desc, emoticon);\n            }\n        }\n    }\n\n    private List<Emoticon> getPageData(int page) {\n        int startIndex = page * PAGE_EMOTICON_SIZE;\n        int endIndex = startIndex + PAGE_EMOTICON_SIZE;\n        if (endIndex > mEmoticonList.size()) {\n            endIndex = mEmoticonList.size();\n        }\n        List<Emoticon> subList = mEmoticonList.subList(startIndex, endIndex);\n        List<Emoticon> list = new ArrayList<>(subList);\n        //追加删除项\n        Emoticon emoticon = new Emoticon();\n        emoticon.setId(R.drawable.emoji_item_delete);\n        emoticon.setDesc(mContext.getString(R.string.desc_emoticon_delete));\n        emoticon.setName(mContext.getString(R.string.name_emoticon_delete));\n        list.add(emoticon);\n        return list;\n    }\n\n    public List<List<Emoticon>> getEmoticonPageList() {\n        if ((mEmoticonPageList == null || mEmoticonPageList.size() <= 0) && mContext != null) {\n            init(mContext);\n        }\n        return mEmoticonPageList;\n    }\n\n    /**\n     * 添加表情\n     *\n     * @param context\n     * @param imgId\n     * @param text\n     * @return\n     */\n    public SpannableString addEmoticon(Context context, int imgId, String text) {\n        if (TextUtils.isEmpty(text)) {\n            return null;\n        }\n        if (mEmojiSet != null && mEmojiSet.contains(text)) {\n            text = EmoticonUtils.EmojiCodeToString(Integer.parseInt(text, 16));\n        }\n        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), imgId);\n        bitmap = Bitmap.createScaledBitmap(bitmap, 64, 64, true);\n        ImageSpan imageSpan = new ImageSpan(context, bitmap);\n        SpannableString spannableString = new SpannableString(text + \" \");\n        spannableString.setSpan(imageSpan, 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);\n        return spannableString;\n    }\n\n    /**\n     * 获取包含表情的文本，表情采用自定义大小\n     *\n     * @param text\n     * @return\n     */\n    public SpannableString getEmoticonStr(CharSequence text) {\n        if (TextUtils.isEmpty(text)) {\n            return new SpannableString(\"\");\n        }\n        String[] regexes = new String[]{REGEX_CONTAIN_EMOTION};\n        SpannableString spannableString = new SpannableString(text);\n        for (String regex : regexes) {\n            dealEmoticon(spannableString, regex);\n        }\n\n        return spannableString;\n    }\n\n    private void dealEmoticon(SpannableString spannableString,  String regex) {\n        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);\n        Matcher matcher = pattern.matcher(spannableString);\n        while (matcher.find()) {\n            String key = matcher.group();\n            Emoticon emoticon = mEmoticons.get(key);\n\n            if (emoticon == null) {\n                continue;\n            }\n\n            @SuppressWarnings(\"deprecation\")\n            ImageSpan imageSpan = new ImageSpan(emoticon.getBitmap());\n            int end = matcher.start() + key.length();\n            spannableString.setSpan(imageSpan, matcher.start(), end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n        }\n    }\n\n    private static class InstanceContainer {\n        private final static EmoticonManager ISNATNCE = new EmoticonManager();\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/EmoticonPanel.java",
    "content": "package com.beetle.bauhinia.toolbar.emoticon;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.viewpager.widget.ViewPager;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport android.text.SpannableString;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.LinearLayout;\n\nimport com.beetle.imlib.R;\nimport com.beetle.bauhinia.tools.DisplayUtils;\nimport com.beetle.bauhinia.toolbar.EaseExpandRecylerView;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Desc.\n *\n * @author chenxj(陈贤靖)\n * @date 2019/3/11\n */\npublic class EmoticonPanel extends FrameLayout {\n\n    private final static int DEFAULT_COLUMNS = 8;\n    /**\n     * 指示器圆点半径，选中和未选中，单位:dp\n     */\n    private final static int HALF_SELECTED_DOT_DISTANCE = 4;\n    private final static int HALF_NORMAL_DOT_DISTANCE = 3;\n    private Context mContext;\n    private EmoticonManager mEmoticonManager;\n    private List<List<Emoticon>> mEmoticonPageList;\n    private ViewPager mVpEmoticon;\n    private LinearLayout mLlIndicator;\n    private ArrayList<View> mEmoticonViewList;\n    private ArrayList<EmoticonAdapter> mEmoticonAdapterList;\n    private ArrayList<View> mIndicatorViewList;\n    /**\n     * 列数\n     */\n    private int mColumns;\n    /**\n     * 当前所在页数\n     */\n    private int mCurPage;\n\n    private OnItemEmoticonClickListener mOnItemEmoticonClickListener;\n\n    public EmoticonPanel(@NonNull Context context) {\n        this(context, null);\n    }\n\n    public EmoticonPanel(@NonNull Context context, @Nullable AttributeSet attrs) {\n        super(context, attrs, 0);\n        init(context, attrs);\n    }\n\n    public void init(Context context, @Nullable AttributeSet attrs) {\n        initData(context, attrs);\n        initView(context);\n        initViewPager();\n        //指示器的初始化放在ViewPager初始化之后，因为要根据ViewPager子View数量确定指示器view个数\n        initIndicator();\n    }\n\n    private void initData(Context context, @Nullable AttributeSet attrs) {\n        mContext = context;\n        mEmoticonManager = EmoticonManager.getInstance();\n        mEmoticonPageList = mEmoticonManager.getEmoticonPageList();\n        mEmoticonViewList = new ArrayList<>();\n        mEmoticonAdapterList = new ArrayList<EmoticonAdapter>();\n        mIndicatorViewList = new ArrayList<>();\n        if (attrs != null) {\n            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.EmoticonPanel);\n            mColumns = typedArray.getInt(R.styleable.EmoticonPanel_emoticonColumns, DEFAULT_COLUMNS);\n            typedArray.recycle();\n        } else {\n            mColumns = DEFAULT_COLUMNS;\n        }\n    }\n\n    private void initView(Context context) {\n        LayoutInflater.from(context).inflate(R.layout.emoticon_view, this);\n        mVpEmoticon = (ViewPager) findViewById(R.id.vp_emoticon);\n        mLlIndicator = (LinearLayout) findViewById(R.id.ll_emoticon_indicator);\n    }\n\n    private void initViewPager() {\n        for (int i = 0; i < mEmoticonPageList.size(); i++) {\n            View view = LayoutInflater.from(mContext).inflate(R.layout.item_emoticon_page, null);\n            EaseExpandRecylerView recylerView = (EaseExpandRecylerView) view.findViewById(R.id.rv_emoticon);\n            GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext, mColumns);\n            recylerView.setLayoutManager(gridLayoutManager);\n            EmoticonAdapter emoticonAdapter = new EmoticonAdapter(mContext, mEmoticonPageList.get(i));\n            recylerView.setAdapter(emoticonAdapter);\n            emoticonAdapter.setOnClickListener(new EmoticonAdapter.OnItemClickListener() {\n                @Override\n                public void onClick(int position) {\n                    Emoticon emoticon = mEmoticonAdapterList.get(mCurPage).getItem(position);\n                    if (emoticon == null || mOnItemEmoticonClickListener == null) {\n                        return;\n                    }\n                    if (emoticon.getId() == R.drawable.emoji_item_delete) {\n                        mOnItemEmoticonClickListener.onEmoticonDeleted();\n                    } else {\n                        if (!TextUtils.isEmpty(emoticon.getDesc())) {\n                            SpannableString spannableString = mEmoticonManager.addEmoticon(\n                                    mContext, emoticon.getId(), emoticon.getDesc());\n                            mOnItemEmoticonClickListener.onEmoticonClick(spannableString);\n                        }\n                    }\n                }\n            });\n            mEmoticonAdapterList.add(emoticonAdapter);\n            mEmoticonViewList.add(view);\n        }\n        mVpEmoticon.setAdapter(new ViewPagerAdapter(mEmoticonViewList));\n        mVpEmoticon.setCurrentItem(0);\n        mCurPage = 0;\n        mVpEmoticon.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {\n            @Override\n            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\n\n            }\n\n            @Override\n            public void onPageSelected(int position) {\n                mCurPage = position;\n                drawIndicatorViews(position);\n            }\n\n            @Override\n            public void onPageScrollStateChanged(int state) {\n\n            }\n        });\n    }\n\n    private void initIndicator() {\n        View view;\n        int distance = DisplayUtils.dp2px(mContext, HALF_NORMAL_DOT_DISTANCE);\n        int length = 2 * distance;\n        for (int i = 0; i < mEmoticonViewList.size(); i++) {\n            view = new View(mContext);\n            view.setBackgroundResource(R.drawable.bg_indicator_dot);\n            view.setEnabled(false);\n            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(length, length);\n            layoutParams.leftMargin = distance;\n            layoutParams.rightMargin = distance;\n            mLlIndicator.addView(view, layoutParams);\n            mIndicatorViewList.add(view);\n        }\n        drawIndicatorViews(0);\n    }\n\n    private void drawIndicatorViews(int index) {\n        int selectedDistance = DisplayUtils.dp2px(mContext, HALF_SELECTED_DOT_DISTANCE) * 2;\n        int normalDistance = DisplayUtils.dp2px(mContext, HALF_NORMAL_DOT_DISTANCE) * 2;\n        int widthAndHeight;\n        for (int i = 0; i < mIndicatorViewList.size(); i++) {\n            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mIndicatorViewList.get(i).getLayoutParams();\n            if (index == i) {\n                mIndicatorViewList.get(i).setEnabled(true);\n                widthAndHeight = selectedDistance;\n            } else {\n                widthAndHeight = normalDistance;\n                mIndicatorViewList.get(i).setEnabled(false);\n            }\n            layoutParams.width = widthAndHeight;\n            layoutParams.height = widthAndHeight;\n            mIndicatorViewList.get(i).setLayoutParams(layoutParams);\n        }\n    }\n\n\n    public void setOnItemEmoticonClickListener(OnItemEmoticonClickListener emoticonClickListener) {\n        mOnItemEmoticonClickListener = emoticonClickListener;\n    }\n\n    public interface OnItemEmoticonClickListener {\n\n        void onEmoticonClick(SpannableString spannableString);\n\n        void onEmoticonDeleted();\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/EmoticonUtils.java",
    "content": "package com.beetle.bauhinia.toolbar.emoticon;\n\nimport android.content.Context;\nimport android.view.WindowManager;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Desc.\n *\n * @author chenxj(陈贤靖)\n * @date 2019/3/11\n */\npublic class EmoticonUtils {\n    /**\n     * 表情配置文件名\n     */\n    public final static String FILE_EMOTICON = \"emoticon\";\n    private static final String TAG = \"EmoticonUtils\";\n\n\n    public static String emojiResources[] = {\n            \"1f60a\",  \"ee_1\",\n            \"1f603\",  \"ee_2\",\n            \"1f609\",  \"ee_3\",\n            \"1f62e\",  \"ee_4\",\n            \"1f60b\",  \"ee_5\",\n            \"1f60e\",  \"ee_6\",\n            \"1f621\",  \"ee_7\",\n            \"1f616\",  \"ee_8\",\n            \"1f633\",  \"ee_9\",\n            \"1f61e\",  \"ee_10\",\n            \"1f62d\",  \"ee_11\",\n            \"1f610\",  \"ee_12\",\n            \"1f607\",  \"ee_13\",\n            \"1f62c\",  \"ee_14\",\n            \"1f606\",  \"ee_15\",\n            \"1f631\",  \"ee_16\",\n            \"1f385\",  \"ee_17\",\n            \"1f634\",  \"ee_18\",\n            \"1f615\",  \"ee_19\",\n            \"1f637\",  \"ee_20\",\n            \"1f62f\",  \"ee_21\",\n            \"1f60f\",  \"ee_22\",\n            \"1f611\",  \"ee_23\",\n            \"1f496\",  \"ee_24\",\n            \"1f494\",  \"ee_25\",\n            \"1f319\",  \"ee_26\",\n            \"1f31f\",  \"ee_27\",\n            \"1f31e\",  \"ee_28\",\n            \"1f308\",  \"ee_29\",\n            \"1f60d\",  \"ee_30\",\n            \"1f61a\",  \"ee_31\",\n            \"1f48b\",  \"ee_32\",\n            \"1f339\",  \"ee_33\",\n            \"1f342\",  \"ee_34\",\n            \"1f44d\",  \"ee_35\",};\n\n\n\n    private static final Map<String, String> emojiMap = new HashMap<String, String>();\n    private static final Set<String> emojiSet = new HashSet<>();\n\n    static {\n        for (int i = 0; i < emojiResources.length; i+=2) {\n            emojiMap.put(emojiResources[i], emojiResources[i+1]);\n            emojiSet.add(emojiResources[i]);\n        }\n    }\n\n\n\n    private static int EMOJI_CODE_TO_SYMBOL(int x) {\n        return ((((0x808080F0 | (x & 0x3F000) >> 4) | (x & 0xFC0) << 10) | (x & 0x1C0000) << 18) | (x & 0x3F) << 24);\n    }\n\n    public static String EmojiCodeToString(int x) {\n        int sym = EMOJI_CODE_TO_SYMBOL(x);\n        byte data[] = {(byte)(sym&0x00ff), (byte)(sym>>8&0x00ff), (byte)(sym>>16&0x00ff), (byte)(sym>>24)};\n        try {\n            return new String(data, 0, 4, \"UTF-8\");\n        } catch (Exception e) {\n            e.printStackTrace();\n            return \"\";\n        }\n    }\n\n\n    public static List<String> readFile(Context context, String fileName) {\n        List<String> lineList = new ArrayList<>();\n        try {\n            InputStream inputStream = context.getResources().getAssets().open(fileName);\n            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));\n            String str = \"\";\n            while ((str = reader.readLine()) != null) {\n                lineList.add(str);\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return lineList;\n    }\n\n\n\n\n\n    public static Map<String, String> getEmojiMap() {\n            return emojiMap;\n    }\n\n    public static Set<String> getEmojiEncodeSet() {\n        return emojiSet;\n    }\n\n    private static int getEmoticonSize(Context context) {\n        if (context == null) {\n            return 0;\n        }\n        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);\n        if (wm != null) {\n            return wm.getDefaultDisplay().getHeight() * 200 / 1920;\n        }\n        return 0;\n    }\n\n    public static int getNormalSize(Context context) {\n        return getEmoticonSize(context);\n    }\n\n    public static int getSmallSize(Context context) {\n        return getEmoticonSize(context) / 4 * 3;\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/ViewPagerAdapter.java",
    "content": "package com.beetle.bauhinia.toolbar.emoticon;\n\nimport androidx.annotation.NonNull;\nimport androidx.viewpager.widget.PagerAdapter;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport java.util.List;\n\n/**\n * Desc.\n *\n * @author chenxj(陈贤靖)\n * @date 2019/3/11\n */\npublic class ViewPagerAdapter extends PagerAdapter {\n\n    private List<View> mPageViewList;\n\n    public ViewPagerAdapter(List<View> pageViewList) {\n        this.mPageViewList = pageViewList;\n    }\n\n    @Override\n    public int getItemPosition(@NonNull Object object) {\n        return super.getItemPosition(object);\n    }\n\n    @Override\n    public int getCount() {\n        return mPageViewList.size();\n    }\n\n    @Override\n    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {\n        return view == object;\n    }\n\n    @Override\n    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {\n        container.removeView(mPageViewList.get(position));\n    }\n\n    @NonNull\n    @Override\n    public Object instantiateItem(@NonNull ViewGroup container, int position) {\n        container.addView(mPageViewList.get(position));\n        return mPageViewList.get(position);\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/tools/DisplayUtils.java",
    "content": "package com.beetle.bauhinia.tools;\n\nimport android.content.Context;\nimport android.util.TypedValue;\n\n/**\n * Created by hillwind\n */\npublic class DisplayUtils {\n\n    public static int getSizeByGivenAbsSize(Context context, int givenAbsSize) {\n        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, givenAbsSize, context.getResources().getDisplayMetrics());\n    }\n\n    public static int getScreenWidth(Context context) {\n        return context.getResources().getDisplayMetrics().widthPixels;\n    }\n\n    public static int getScreenHeight(Context context) {\n        return context.getResources().getDisplayMetrics().heightPixels;\n    }\n\n    public static float getScreenDensity(Context context) {\n        return context.getResources().getDisplayMetrics().density;\n    }\n\n    public static int getScreenDensityDpi(Context context) {\n        return context.getResources().getDisplayMetrics().densityDpi;\n    }\n\n    public static int dp2px(Context context, float dp) {\n        final float scale = context.getResources().getDisplayMetrics().density;\n        return (int) (dp * scale + 0.5f);\n    }\n\n    public static int px2dp(Context context, float px) {\n        final float scale = context.getResources().getDisplayMetrics().density;\n        return (int) (px / scale + 0.5f);\n    }\n\n    public static int px2sp(Context context, float pxValue) {\n        return (int) (pxValue / context.getResources().getDisplayMetrics().scaledDensity + 0.5f);\n    }\n\n    public static int sp2px(Context context, float spValue) {\n        return (int) (spValue * context.getResources().getDisplayMetrics().scaledDensity + 0.5f);\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageAudioView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.graphics.drawable.AnimationDrawable;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.beetle.bauhinia.db.message.Audio;\nimport org.joda.time.Period;\nimport org.joda.time.format.PeriodFormatter;\nimport org.joda.time.format.PeriodFormatterBuilder;\nimport java.beans.PropertyChangeEvent;\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.imlib.R;\n\npublic class MessageAudioView extends MessageContentView {\n    public MessageAudioView(Context context) {\n        super(context);\n        inflater.inflate(R.layout.chat_content_audio, this);\n    }\n\n    class AudioHolder  {\n        ImageView control;\n        TextView duration;\n        ImageView listen;\n\n        AudioHolder(View view) {\n            control = (ImageView)view.findViewById(R.id.play_control);\n            duration = (TextView)view.findViewById(R.id.duration);\n            listen = (ImageView)view.findViewById(R.id.listen);\n        }\n    }\n\n    @Override\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n\n        boolean playing = message.getPlaying();\n        View convertView = this;\n\n        final Audio audio = (Audio) msg.content;\n        AudioHolder audioHolder =  new AudioHolder(convertView);\n\n        if (playing) {\n            AnimationDrawable voiceAnimation;\n            if (!msg.isOutgoing) {\n                audioHolder.control.setImageResource(R.drawable.voice_from_icon);\n            } else {\n                audioHolder.control.setImageResource(R.drawable.voice_to_icon);\n            }\n            voiceAnimation = (AnimationDrawable) audioHolder.control.getDrawable();\n            voiceAnimation.start();\n        } else {\n            if (!msg.isOutgoing) {\n                audioHolder.control.setImageResource(R.drawable.ease_chatfrom_voice_playing);\n            } else {\n                audioHolder.control.setImageResource(R.drawable.ease_chatto_voice_playing);\n            }\n        }\n\n        Period period = new Period().withSeconds((int) audio.duration);\n        PeriodFormatter periodFormatter = new PeriodFormatterBuilder()\n                .appendMinutes()\n                .appendSeparator(\":\")\n                .appendSeconds()\n                .appendSuffix(\"\\\"\")\n                .toFormatter();\n        audioHolder.duration.setText(periodFormatter.print(period));\n\n        if (!msg.isListened() && !msg.isOutgoing) {\n            audioHolder.listen.setVisibility(VISIBLE);\n        } else {\n            audioHolder.listen.setVisibility(GONE);\n        }\n\n        convertView.requestLayout();\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        super.propertyChange(event);\n        if (event.getPropertyName().equals(\"playing\")) {\n            Log.i(\"gobelieve\", \"playing changed\");\n            boolean playing = this.message.getPlaying();\n            AudioHolder audioHolder =  new AudioHolder(this);\n            if (playing) {\n                AnimationDrawable voiceAnimation;\n                if (!this.message.isOutgoing) {\n                    audioHolder.control.setImageResource(R.drawable.voice_from_icon);\n                } else {\n                    audioHolder.control.setImageResource(R.drawable.voice_to_icon);\n                }\n                voiceAnimation = (AnimationDrawable) audioHolder.control.getDrawable();\n                voiceAnimation.start();\n                Log.i(\"gobelieve\", \"start animation\");\n            } else {\n                if (!this.message.isOutgoing) {\n                    audioHolder.control.setImageResource(R.drawable.ease_chatfrom_voice_playing);\n                } else {\n                    audioHolder.control.setImageResource(R.drawable.ease_chatto_voice_playing);\n                }\n            }\n        } else if (event.getPropertyName().equals(\"listened\")) {\n            AudioHolder audioHolder =  new AudioHolder(this);\n            if (!this.message.isListened() && !this.message.isOutgoing) {\n                audioHolder.listen.setVisibility(VISIBLE);\n            } else {\n                audioHolder.listen.setVisibility(GONE);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageClassroomView.java",
    "content": "package com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.imlib.R;\n\n//support conference&classroom message\npublic class MessageClassroomView extends MessageContentView {\n    public MessageClassroomView(Context context) {\n        super(context);\n        inflater.inflate(R.layout.chat_content_voip, this);\n    }\n\n    @Override\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n        ImageView v = findViewById(R.id.phone);\n\n        if (msg.getType() == MessageContent.MessageType.MESSAGE_CLASSROOM) {\n            v.setImageResource(R.drawable.classroom);\n            String text = \"发起了群课堂\";\n            TextView content = (TextView) findViewById(R.id.text);\n            content.setText(text);\n        } else {\n            v.setImageResource(R.drawable.conference);\n            String text = \"发起了视频会议\";\n            TextView content = (TextView) findViewById(R.id.text);\n            content.setText(text);\n        }\n        requestLayout();\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageContentView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.widget.FrameLayout;\nimport com.beetle.bauhinia.db.IMessage;\n\nimport java.beans.PropertyChangeEvent;\nimport java.beans.PropertyChangeListener;\n\npublic class MessageContentView extends FrameLayout implements PropertyChangeListener {\n    protected Context context;\n    LayoutInflater inflater;\n    protected IMessage message;\n\n    public MessageContentView(Context context) {\n        super(context);\n        this.context = context;\n        inflater = LayoutInflater.from(context);\n    }\n\n    public void setMessage(IMessage msg) {\n        if (this.message != null) {\n            this.message.removePropertyChangeListener(this);\n        }\n        this.message = msg;\n        this.message.addPropertyChangeListener(this);\n    }\n\n    public IMessage getMessage() {\n        return message;\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageFileView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\nimport com.beetle.bauhinia.db.message.File;\n\nimport java.beans.PropertyChangeEvent;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.imlib.R;\n\npublic class MessageFileView extends MessageContentView {\n    protected ProgressBar uploadingProgressBar;\n    protected View maskView;\n\n    public MessageFileView(Context context) {\n        super(context);\n        int contentLayout = R.layout.chat_content_file;\n        inflater.inflate(contentLayout, this);\n\n        uploadingProgressBar = (ProgressBar)findViewById(R.id.progress_bar);\n\n        maskView = findViewById(R.id.mask);\n    }\n\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n        File fileMsg = (File) msg.content;\n\n        String filename = fileMsg.filename;\n        ImageView imageView = (ImageView)findViewById(R.id.image);\n        if (filename.endsWith(\".doc\") || filename.endsWith(\"docx\")) {\n            imageView.setImageResource(R.drawable.word);\n        } else if (filename.endsWith(\".xls\") || filename.endsWith(\".xlsx\")) {\n            imageView.setImageResource(R.drawable.excel);\n        } else if (filename.endsWith(\".pdf\")) {\n            imageView.setImageResource(R.drawable.pdf);\n        } else {\n            imageView.setImageResource(R.drawable.file);\n        }\n\n        TextView titleView = (TextView)findViewById(R.id.title);\n        titleView.setText(fileMsg.filename);\n\n        TextView contentView = (TextView)findViewById(R.id.descreption);\n        contentView.setText(formatSize(fileMsg.size));\n\n        boolean uploading = msg.getUploading();\n        boolean downloading = msg.getDownloading();\n        if (uploading || downloading) {\n            maskView.setVisibility(View.VISIBLE);\n            uploadingProgressBar.setVisibility(View.VISIBLE);\n        } else {\n            maskView.setVisibility(View.GONE);\n            uploadingProgressBar.setVisibility(View.GONE);\n        }\n        requestLayout();\n    }\n\n\n    private String formatSize(int size) {\n        if (size < 1024) {\n            return String.format(\"%d字节\", size);\n        } else if (size < 1024*1024){\n            return String.format(\"%.1fKB\",  size*1.0/1024);\n        } else {\n            return String.format(\"%.1fMB\", size*1.0/(1024*1024));\n        }\n    }\n\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        super.propertyChange(event);\n        if (event.getPropertyName().equals(\"uploading\") ||\n                event.getPropertyName().equals(\"downloading\")) {\n            boolean uploading = this.message.getUploading();\n            boolean downloading = this.message.getDownloading();\n            if (uploading || downloading) {\n                maskView.setVisibility(View.VISIBLE);\n                uploadingProgressBar.setVisibility(View.VISIBLE);\n            } else {\n                maskView.setVisibility(View.GONE);\n                uploadingProgressBar.setVisibility(View.GONE);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageImageView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.ProgressBar;\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.Image;\nimport com.beetle.imlib.R;\nimport com.squareup.picasso.Picasso;\n\nimport java.beans.PropertyChangeEvent;\n\npublic class MessageImageView extends MessageContentView {\n\n    protected ProgressBar uploadingProgressBar;\n    protected View maskView;\n\n    public MessageImageView(Context context) {\n        super(context);\n        int contentLayout = R.layout.chat_content_image;\n        inflater.inflate(contentLayout, this);\n        uploadingProgressBar = (ProgressBar)findViewById(R.id.progress_bar);\n        maskView = findViewById(R.id.mask);\n    }\n\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n\n        ImageView imageView = (ImageView)findViewById(R.id.image);\n\n        String url = ((Image) msg.content).url;\n        if (msg.secret) {\n            if (!url.startsWith(\"file:\")) {\n                url = \"\";\n            }\n            Picasso.get()\n                    .load(url)\n                    .placeholder(R.drawable.image_download_fail)\n                    .into(imageView);\n        } else {\n            Picasso.get()\n                    .load(url + \"@256w_256h_0c\")\n                    .placeholder(R.drawable.image_download_fail)\n                    .into(imageView);\n        }\n\n        boolean uploading = msg.getUploading();\n        boolean downloading = msg.getDownloading();\n        if (uploading || downloading) {\n            if (maskView != null) {\n                maskView.setVisibility(View.VISIBLE);\n            }\n            uploadingProgressBar.setVisibility(View.VISIBLE);\n        } else {\n            if (maskView != null) {\n                maskView.setVisibility(View.GONE);\n            }\n            uploadingProgressBar.setVisibility(View.GONE);\n        }\n\n        requestLayout();\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        super.propertyChange(event);\n        if (event.getPropertyName().equals(\"uploading\") ||\n                event.getPropertyName().equals(\"downloading\")) {\n            boolean uploading = this.message.getUploading();\n            boolean downloading = this.message.getDownloading();\n            if (uploading || downloading) {\n                if (maskView != null) {\n                    maskView.setVisibility(View.VISIBLE);\n                }\n                uploadingProgressBar.setVisibility(View.VISIBLE);\n            } else {\n                if (maskView != null) {\n                    maskView.setVisibility(View.GONE);\n                }\n                uploadingProgressBar.setVisibility(View.GONE);\n            }\n        }\n\n        ImageView imageView = (ImageView)findViewById(R.id.image);\n        if (event.getPropertyName().equals(\"downloading\")) {\n            if (message.secret) {\n                String url = ((Image) message.content).url;\n                if (!url.startsWith(\"file:\")) {\n                    url = \"\";\n                }\n                Picasso.get()\n                        .load(url)\n                        .placeholder(R.drawable.image_download_fail)\n                        .into(imageView);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageLinkView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport com.beetle.bauhinia.db.message.Link;\nimport com.squareup.picasso.Picasso;\nimport java.beans.PropertyChangeEvent;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.imlib.R;\n\npublic class MessageLinkView extends MessageContentView {\n\n    public MessageLinkView(Context context) {\n        super(context);\n        int contentLayout = R.layout.chat_content_link;\n        inflater.inflate(contentLayout, this);\n    }\n\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n        Link linkMsg = (Link) msg.content;\n\n        ImageView imageView = (ImageView)findViewById(R.id.image);\n        Picasso.get()\n                .load(linkMsg.image)\n                .placeholder(R.drawable.image_download_fail)\n                .into(imageView);\n\n        TextView titleView = (TextView)findViewById(R.id.title);\n        titleView.setText(linkMsg.title);\n\n        TextView contentView = (TextView)findViewById(R.id.descreption);\n        contentView.setText(linkMsg.content);\n\n        requestLayout();\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        super.propertyChange(event);\n\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageLocationView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.Location;\nimport com.beetle.imlib.R;\n\nimport java.beans.PropertyChangeEvent;\n\npublic class MessageLocationView extends MessageContentView {\n\n    protected ProgressBar progressBar;\n\n    public MessageLocationView(Context context) {\n        super(context);\n\n        int contentLayout = R.layout.chat_content_location;\n        inflater.inflate(contentLayout, this);\n        progressBar = (ProgressBar)findViewById(R.id.progress_bar);\n    }\n\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n        Location loc = (Location)this.message.content;\n        if (loc.address != null && loc.address.length() > 0) {\n            TextView content = (TextView) findViewById(R.id.text);\n            content.setText(loc.address);\n        }\n        boolean geocoding = this.message.getGeocoding();\n        if (geocoding) {\n            progressBar.setVisibility(View.VISIBLE);\n        } else {\n            progressBar.setVisibility(View.GONE);\n        }\n        requestLayout();\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        super.propertyChange(event);\n        if (event.getPropertyName().equals(\"geocoding\")) {\n            boolean geocoding = this.message.getGeocoding();\n            if (geocoding) {\n                progressBar.setVisibility(View.VISIBLE);\n            } else {\n                progressBar.setVisibility(View.GONE);\n                Location loc = (Location)this.message.content;\n                if (loc.address != null && loc.address.length() > 0) {\n                    TextView content = (TextView) findViewById(R.id.text);\n                    content.setText(loc.address);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageNotificationView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.widget.TextView;\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.Notification;\nimport com.beetle.imlib.R;\n\nimport java.beans.PropertyChangeEvent;\n\npublic class MessageNotificationView extends MessageContentView {\n    public MessageNotificationView(Context context) {\n        super(context);\n        final int contentLayout;\n        contentLayout = R.layout.chat_content_small_text;\n        inflater.inflate(contentLayout, this);\n    }\n\n    @Override\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n\n        TextView content = (TextView) findViewById(R.id.text);\n        String text = ((Notification) msg.content).getDescription();\n        content.setText(text);\n\n        requestLayout();\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        super.propertyChange(event);\n        if (event.getPropertyName().equals(\"downloading\")) {\n            TextView content = (TextView) findViewById(R.id.text);\n            String text = ((Notification) this.message.content).getDescription();\n            content.setText(text);\n        }\n    }\n\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageTextView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.widget.TextView;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Text;\nimport com.beetle.imlib.R;\n\npublic class MessageTextView extends MessageContentView {\n    private TextView textView;\n\n    public MessageTextView(Context context) {\n        super(context);\n        inflater.inflate(R.layout.chat_content_text, this);\n        textView = (TextView)findViewById(R.id.text);\n    }\n\n    @Override\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n\n        this.textView.setTag(message);\n\n        MessageContent.MessageType mediaType = message.content.getType();\n\n        if (mediaType == MessageContent.MessageType.MESSAGE_TEXT) {\n            TextView content = (TextView) findViewById(R.id.text);\n            Text text = (Text)msg.content;\n            if (text.spanText != null) {\n                content.setText(text.spanText);\n            } else {\n                content.setText(text.text);\n            }\n        }\n        requestLayout();\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageUnknownView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.widget.TextView;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.imlib.R;\n\npublic class MessageUnknownView extends MessageContentView {\n    public MessageUnknownView(Context context) {\n        super(context);\n        inflater.inflate(R.layout.chat_content_text, this);\n    }\n    @Override\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n        TextView content = (TextView) findViewById(R.id.text);\n        if (msg.getType() == MessageContent.MessageType.MESSAGE_SECRET) {\n            content.setText(\"消息未能解密\");\n        } else {\n            content.setText(\"未知的消息类型\");\n        }\n        requestLayout();\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageVOIPView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.VOIP;\nimport com.beetle.imlib.R;\n\n/**\n * Created by houxh on 2017/11/14.\n */\n\npublic class MessageVOIPView extends MessageContentView {\n\n    public MessageVOIPView(Context context) {\n        super(context);\n        inflater.inflate(R.layout.chat_content_voip, this);\n    }\n\n    @Override\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n\n        VOIP voip = (VOIP)msg.content;\n\n        int m = voip.duration/60;\n        int s = voip.duration%60;\n        String t = String.format(\"%02d:%02d\", m, s);\n\n        String text = \"未知状态\";\n        if (msg.isOutgoing) {\n            switch (voip.flag) {\n                case VOIP.VOIP_FLAG_ACCEPTED:\n                    text = t;\n                    break;\n                case VOIP.VOIP_FLAG_REFUSED:\n                    text = \"对方已拒绝\";\n                    break;\n                case VOIP.VOIP_FLAG_CANCELED:\n                    text = \"已取消\";\n                    break;\n                case VOIP.VOIP_FLAG_UNRECEIVED:\n                    text = \"对方未接听\";\n                    break;\n            }\n        } else {\n            switch (voip.flag) {\n                case VOIP.VOIP_FLAG_ACCEPTED:\n                    text = t;\n                    break;\n                case VOIP.VOIP_FLAG_REFUSED:\n                    text = \"已拒绝\";\n                    break;\n                case VOIP.VOIP_FLAG_CANCELED:\n                    text = \"对方已取消\";\n                    break;\n                case VOIP.VOIP_FLAG_UNRECEIVED:\n                    text = \"未接听\";\n                    break;\n            }\n        }\n        if (msg.isOutgoing) {\n            View v = findViewById(R.id.phone);\n            ViewGroup parent = (ViewGroup)findViewById(R.id.voip);\n            parent.removeView(v);\n            parent.addView(v);\n        }\n\n        MessageContent.MessageType mediaType = message.getType();\n        TextView content = (TextView) findViewById(R.id.text);\n        content.setText(text);\n        requestLayout();\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/java/com/beetle/bauhinia/view/MessageVideoView.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.bauhinia.view;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.ProgressBar;\n\nimport android.widget.TextView;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.Video;\nimport com.beetle.imlib.R;\nimport com.squareup.picasso.Picasso;\nimport org.joda.time.Period;\nimport org.joda.time.format.PeriodFormatter;\nimport org.joda.time.format.PeriodFormatterBuilder;\n\nimport java.beans.PropertyChangeEvent;\n\npublic class MessageVideoView extends MessageContentView {\n\n    protected ProgressBar uploadingProgressBar;\n    protected View maskView;\n    protected TextView durationView;\n    protected View playView;\n\n    public MessageVideoView(Context context) {\n        super(context);\n        int contentLayout = R.layout.chat_content_video;\n        inflater.inflate(contentLayout, this);\n        uploadingProgressBar = (ProgressBar)findViewById(R.id.progress_bar);\n\n        maskView = findViewById(R.id.mask);\n        durationView = (TextView)findViewById(R.id.duration);\n        playView = findViewById(R.id.play);\n    }\n\n    public void setMessage(IMessage msg) {\n        super.setMessage(msg);\n\n        ImageView imageView = (ImageView)findViewById(R.id.image);\n\n        Video video = (Video)msg.content;\n        String url = video.thumbnail;\n        if (msg.secret) {\n            if (!url.startsWith(\"file:\")) {\n                url = \"\";\n            }\n            Picasso.get()\n                    .load(url)\n                    .placeholder(R.drawable.image_download_fail)\n                    .into(imageView);\n        } else {\n            Picasso.get()\n                    .load(url)\n                    .placeholder(R.drawable.image_download_fail)\n                    .into(imageView);\n        }\n\n        Period period = new Period().withSeconds(video.duration);\n        PeriodFormatter periodFormatter = new PeriodFormatterBuilder()\n                .appendMinutes()\n                .appendSeparator(\":\")\n                .appendSeconds()\n                .appendSuffix(\"\\\"\")\n                .toFormatter();\n        durationView.setText(periodFormatter.print(period));\n\n\n        boolean uploading = msg.getUploading();\n        boolean downloading = msg.getDownloading();\n        if (uploading || downloading) {\n            if (maskView != null) {\n                maskView.setVisibility(View.VISIBLE);\n            }\n            uploadingProgressBar.setVisibility(View.VISIBLE);\n            playView.setVisibility(View.GONE);\n        } else {\n            if (maskView != null) {\n                maskView.setVisibility(View.GONE);\n            }\n            uploadingProgressBar.setVisibility(View.GONE);\n            playView.setVisibility(View.VISIBLE);\n        }\n\n        requestLayout();\n    }\n\n    @Override\n    public void propertyChange(PropertyChangeEvent event) {\n        super.propertyChange(event);\n        if (event.getPropertyName().equals(\"uploading\") ||\n                event.getPropertyName().equals(\"downloading\")) {\n            boolean uploading = this.message.getUploading();\n            boolean downloading = this.message.getDownloading();\n            if (uploading || downloading) {\n                if (maskView != null) {\n                    maskView.setVisibility(View.VISIBLE);\n                }\n                uploadingProgressBar.setVisibility(View.VISIBLE);\n                playView.setVisibility(View.GONE);\n            } else {\n                if (maskView != null) {\n                    maskView.setVisibility(View.GONE);\n                }\n                uploadingProgressBar.setVisibility(View.GONE);\n                playView.setVisibility(View.VISIBLE);\n            }\n        }\n\n        ImageView imageView = (ImageView)findViewById(R.id.image);\n        if (event.getPropertyName().equals(\"downloading\")) {\n            if (message.secret) {\n                String url = ((Video) message.content).thumbnail;\n                if (!url.startsWith(\"file:\")) {\n                    url = \"\";\n                }\n                Picasso.get()\n                        .load(url)\n                        .placeholder(R.drawable.image_download_fail)\n                        .into(imageView);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "imlib/src/main/res/drawable/bg_indicator_dot.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/bg_indicator_dot_enable\" android:state_enabled=\"true\" />\n    <item android:drawable=\"@drawable/bg_indicator_dot_disable\" android:state_enabled=\"false\" />\n\n</selector>"
  },
  {
    "path": "imlib/src/main/res/drawable/bg_indicator_dot_disable.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\"\n    >\n\n    <solid android:color=\"@color/dot_disable_state\" />\n\n    <corners android:radius=\"3dp\" />\n\n</shape>"
  },
  {
    "path": "imlib/src/main/res/drawable/bg_indicator_dot_enable.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\"\n    >\n\n    <solid android:color=\"@color/dot_enable_state\" />\n\n    <corners android:radius=\"4dp\" />\n\n</shape>"
  },
  {
    "path": "imlib/src/main/res/drawable/chatfrom_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_focused=\"true\" android:drawable=\"@drawable/chatfrom_bg_focused\" />\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/chatfrom_bg_pressed\" />\n    <item android:state_selected=\"true\" android:drawable=\"@drawable/chatfrom_bg_focused\" />\n    <item android:drawable=\"@drawable/chatfrom_bg_normal\" />\n</selector>"
  },
  {
    "path": "imlib/src/main/res/drawable/chatto_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_focused=\"true\" android:drawable=\"@drawable/chatto_bg_focused\" />\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/chatto_bg_pressed\" />\n    <item android:state_selected=\"true\" android:drawable=\"@drawable/chatto_bg_focused\" />\n    <item android:drawable=\"@drawable/chatto_bg_normal\" />\n</selector>"
  },
  {
    "path": "imlib/src/main/res/drawable/circle_audio.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n    <solid android:color=\"@color/red\" />\n    <corners android:radius=\"50dp\" />\n</shape>"
  },
  {
    "path": "imlib/src/main/res/drawable/gallery_watch_more_picture_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\">\n        <shape\n            android:shape=\"rectangle\">\n            <stroke android:width=\"1dp\" android:color=\"#ffffff\"/>\n            <solid android:color=\"#808d8d8d\"/>\n            <corners android:radius=\"4dp\"/>\n        </shape>\n    </item>\n    <item>\n        <shape\n            android:shape=\"rectangle\">\n            <stroke android:width=\"1dp\" android:color=\"#80ffffff\"/>\n            <solid android:color=\"#00000000\"/>\n            <corners android:radius=\"4dp\"/>\n        </shape>\n    </item>\n</selector>\n"
  },
  {
    "path": "imlib/src/main/res/drawable/rounded_corner.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n\n<solid android:color=\"#ffcccccc\" />\n\n<padding\n    android:left=\"1dp\"\n    android:right=\"1dp\"\n    android:top=\"1dp\" />\n\n<corners android:radius=\"2dp\" />\n</shape>"
  },
  {
    "path": "imlib/src/main/res/drawable-mdpi/voice_from_icon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:oneshot=\"false\">\n    <item\n        android:drawable=\"@drawable/ease_chatfrom_voice_playing_f1\"\n        android:duration=\"200\" />\n    <item\n        android:drawable=\"@drawable/ease_chatfrom_voice_playing_f2\"\n        android:duration=\"200\" />\n    <item\n        android:drawable=\"@drawable/ease_chatfrom_voice_playing_f3\"\n        android:duration=\"200\" />\n</animation-list>"
  },
  {
    "path": "imlib/src/main/res/drawable-mdpi/voice_to_icon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:oneshot=\"false\">\n    <item\n        android:drawable=\"@drawable/ease_chatto_voice_playing_f1\"\n        android:duration=\"200\" />\n    <item\n        android:drawable=\"@drawable/ease_chatto_voice_playing_f2\"\n        android:duration=\"200\" />\n    <item\n        android:drawable=\"@drawable/ease_chatto_voice_playing_f3\"\n        android:duration=\"200\" />\n</animation-list>"
  },
  {
    "path": "imlib/src/main/res/layout/chat_content_audio.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"horizontal\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center_vertical\"\n    >\n\n    <ImageView\n        android:id=\"@+id/play_control\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/ease_chatfrom_voice_playing\"/>\n\n    <ProgressBar\n        android:id=\"@+id/progress\"\n        android:visibility=\"invisible\"\n        android:layout_width=\"160dp\"\n        android:layout_height=\"wrap_content\" />\n\n\n\n    <TextView\n        android:id=\"@+id/duration\"\n        android:textColor=\"@android:color/black\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"1s\"\n        />\n\n    <ImageView\n        android:id=\"@+id/listen\"\n        android:layout_width=\"12dp\"\n        android:layout_height=\"12dp\"\n        android:src=\"@drawable/circle_audio\"\n        android:layout_marginLeft=\"4dp\" />\n\n\n</LinearLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/chat_content_file.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        tools:layout_editor_absoluteY=\"81dp\">\n\n    <LinearLayout\n        android:id=\"@+id/linearLayout\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n        <TextView\n                android:id=\"@+id/title\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:maxWidth=\"160dp\"\n                android:ellipsize=\"end\"\n                android:text=\"11122222te\"\n                android:maxLines=\"2\"/>\n        <TextView\n                android:id=\"@+id/descreption\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"4dp\"\n                android:text=\"33333\"/>\n    </LinearLayout>\n\n    <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"48dp\"\n            android:adjustViewBounds=\"true\"\n            android:id=\"@+id/image\"\n            android:scaleType=\"fitCenter\"\n            app:srcCompat=\"@drawable/word\"\n            app:layout_constraintStart_toEndOf=\"@+id/linearLayout\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            android:layout_marginStart=\"8dp\"/>\n\n    <View\n        android:id=\"@+id/mask\"\n        android:background=\"#80ffffff\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:visibility=\"visible\"\n        app:layout_constraintStart_toStartOf=\"@+id/linearLayout\"\n        app:layout_constraintEnd_toEndOf=\"@+id/image\"\n        app:layout_constraintTop_toTopOf=\"@+id/image\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/image\"\n        />\n\n    <ProgressBar\n        android:id=\"@+id/progress_bar\"\n        style=\"?android:attr/progressBarStyleSmall\"\n        android:layout_width=\"28dp\"\n        android:layout_height=\"28dp\"\n        android:visibility=\"visible\"\n        app:layout_constraintStart_toStartOf=\"@+id/linearLayout\"\n        app:layout_constraintEnd_toEndOf=\"@+id/image\"\n        app:layout_constraintTop_toTopOf=\"@+id/image\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/image\"\n        />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/chat_content_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center_horizontal\">\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"160dp\"\n        android:maxWidth=\"225dp\"\n        android:id=\"@+id/image\"\n        android:adjustViewBounds=\"true\"\n        android:scaleType=\"fitCenter\"/>\n    <View\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignLeft=\"@+id/image\"\n        android:layout_alignRight=\"@+id/image\"\n        android:layout_alignBottom=\"@+id/image\"\n        android:layout_alignTop=\"@+id/image\"\n        android:background=\"#80ffffff\"\n        android:id=\"@+id/mask\"\n        android:visibility=\"gone\"\n        />\n\n    <ProgressBar\n        android:id=\"@+id/progress_bar\"\n        style=\"?android:attr/progressBarStyleSmall\"\n        android:layout_width=\"28dp\"\n        android:layout_height=\"28dp\"\n        android:layout_centerInParent=\"true\"\n        android:visibility=\"visible\" />\n\n</RelativeLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/chat_content_link.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    >\n    <TextView\n        android:id=\"@+id/title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"4dip\"\n        android:maxLines=\"2\"/>\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"64dip\"\n        android:adjustViewBounds=\"true\"\n        android:id=\"@+id/image\"\n        android:scaleType=\"fitCenter\"\n        android:layout_below=\"@+id/title\"\n        android:layout_marginRight=\"2dip\"\n        />\n\n    <TextView\n        android:id=\"@+id/descreption\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"64dip\"\n        android:layout_alignTop=\"@+id/image\"\n        android:layout_toRightOf=\"@+id/image\"/>\n\n</RelativeLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/chat_content_location.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"140dp\"\n    android:layout_height=\"wrap_content\">\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"140dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:background=\"@drawable/ease_location_msg\"\n        android:gravity=\"center|left\"\n        android:textSize=\"13sp\"\n        android:textColor=\"@android:color/white\"\n        android:lineSpacingExtra=\"2dp\"\n        android:minHeight=\"50dp\"/>\n\n    <ProgressBar\n        android:id=\"@+id/progress_bar\"\n        android:layout_width=\"25dp\"\n        android:layout_height=\"25dp\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_alignParentBottom=\"true\"\n        android:visibility=\"visible\" />\n\n</RelativeLayout>\n\n"
  },
  {
    "path": "imlib/src/main/res/layout/chat_content_small_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/text\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"match_parent\"\n    android:autoLink=\"web\"\n    android:layout_centerInParent=\"true\"\n    android:gravity=\"center|left\"\n    android:paddingRight=\"6dp\"\n    android:paddingLeft=\"6dp\"\n    android:minHeight=\"24dp\"\n    android:lineSpacingExtra=\"2dp\"\n    android:background=\"@drawable/rounded_corner\"\n    android:textColor=\"@color/white\"\n    android:maxWidth=\"225.0dp\"\n    android:text=\"test\"\n    android:textSize=\"12sp\" />"
  },
  {
    "path": "imlib/src/main/res/layout/chat_content_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<TextView\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:id=\"@+id/text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:autoLink=\"web\"\n        android:layout_centerInParent=\"true\"\n        android:gravity=\"center|left\"\n        android:paddingRight=\"6dp\"\n        android:paddingLeft=\"1dp\"\n        android:minHeight=\"38dp\"\n        android:lineSpacingExtra=\"2dp\"\n        android:textColor=\"#000000\"\n        android:maxWidth=\"225.0dp\"\n        android:textSize=\"15sp\" />\n\n"
  },
  {
    "path": "imlib/src/main/res/layout/chat_content_video.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    >\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"140dip\"\n        android:id=\"@+id/image\"\n        android:adjustViewBounds=\"true\"\n        android:scaleType=\"fitCenter\"/>\n\n    <ImageView android:id=\"@+id/play\"\n               android:layout_width=\"28dp\"\n               android:layout_height=\"28dp\"\n               android:layout_centerInParent=\"true\"\n               android:src=\"@drawable/play\"/>\n\n    <TextView\n            android:id=\"@+id/duration\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignRight=\"@+id/image\"\n            android:layout_alignBottom=\"@+id/image\"\n            android:layout_marginRight=\"4dp\"/>\n\n    <View\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignLeft=\"@+id/image\"\n        android:layout_alignRight=\"@+id/image\"\n        android:layout_alignBottom=\"@+id/image\"\n        android:layout_alignTop=\"@+id/image\"\n        android:background=\"#80ffffff\"\n        android:id=\"@+id/mask\"\n        android:visibility=\"gone\"\n        />\n\n    <ProgressBar\n        android:id=\"@+id/progress_bar\"\n        style=\"?android:attr/progressBarStyleSmall\"\n        android:layout_width=\"28dp\"\n        android:layout_height=\"28dp\"\n        android:layout_centerInParent=\"true\"\n        android:visibility=\"visible\" />\n\n\n\n</RelativeLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/chat_content_voip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/voip\"\n    android:minHeight=\"38dp\"\n    android:orientation=\"horizontal\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:id=\"@+id/phone\"\n        android:layout_height=\"24dp\"\n        android:layout_width=\"48dp\"\n        android:layout_gravity=\"center\"\n        android:src=\"@drawable/phone\"/>\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:paddingRight=\"6dp\"\n        android:paddingLeft=\"1dp\"\n        android:lineSpacingExtra=\"2dp\"\n        android:textColor=\"#000000\"\n        android:maxWidth=\"225.0dp\"\n        android:textSize=\"15sp\"\n        android:text=\"对方未接听\"/>\n\n\n\n</LinearLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/ease_chat_menu_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_horizontal\"\n    android:orientation=\"vertical\" >\n\n    <RelativeLayout\n        android:layout_width=\"64dp\"\n        android:layout_height=\"64dp\" >\n\n        <ImageView\n            android:id=\"@+id/image\"\n            android:layout_width=\"56dp\"\n            android:layout_height=\"56dp\"\n            android:layout_centerInParent=\"true\"\n            android:scaleType=\"fitCenter\" />\n    </RelativeLayout>\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:text=\"@string/attach_take_pic\"\n        android:textColor=\"#727171\" />\n\n</LinearLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/ease_row_expression.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      android:id=\"@+id/iv_expression\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"8dp\"\n        android:scaleType=\"centerInside\">\n\n</ImageView>\n"
  },
  {
    "path": "imlib/src/main/res/layout/ease_widget_chat_input_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:easemob=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/chat_menu_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_alignParentBottom=\"true\"\n    android:orientation=\"vertical\">\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/primary_menu_container\">\n        <com.beetle.bauhinia.toolbar.EaseChatPrimaryMenu\n            android:id=\"@+id/primary_menu\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/extend_menu_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"#fff\"\n        android:visibility=\"gone\" >\n\n        <com.beetle.bauhinia.toolbar.EaseChatExtendMenu\n            android:id=\"@+id/extend_menu\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"7dp\"\n            android:layout_marginTop=\"9dp\"\n            easemob:numColumns=\"4\" />\n\n        <RelativeLayout\n            android:id=\"@+id/menu_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"300dp\"\n            android:background=\"@color/white\">\n\n        </RelativeLayout>\n\n    </FrameLayout>\n\n</LinearLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/ease_widget_chat_primary_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"2dip\"\n    android:paddingTop=\"2dip\" >\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0.5dp\"\n        android:background=\"@color/gray_normal\" />\n\n    <LinearLayout\n        android:id=\"@+id/rl_bottom\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"#aaffffff\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingTop=\"4dp\" >\n\n        <Button\n            android:id=\"@+id/btn_set_mode_voice\"\n            android:layout_width=\"32dp\"\n            android:layout_height=\"32dp\"\n            android:layout_marginLeft=\"6dp\"\n            android:background=\"@drawable/ease_chatting_setmode_voice_btn\"\n            android:visibility=\"visible\" />\n\n        <Button\n            android:id=\"@+id/btn_set_mode_keyboard\"\n            android:layout_width=\"32dp\"\n            android:layout_height=\"32dp\"\n            android:layout_marginLeft=\"6dp\"\n            android:background=\"@drawable/ease_chatting_setmode_keyboard_btn\"\n            android:visibility=\"gone\" />\n\n        <LinearLayout\n            android:id=\"@+id/btn_press_to_speak\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"6dp\"\n            android:layout_marginLeft=\"6dp\"\n            android:layout_marginRight=\"6dp\"\n            android:layout_marginTop=\"6dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/ease_chat_press_speak_btn\"\n            android:gravity=\"center\"\n            android:visibility=\"gone\" >\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"5dp\"\n                android:text=\"@string/button_pushtotalk\"\n                android:textColor=\"#666666\"\n                android:textSize=\"16sp\" />\n        </LinearLayout>\n\n        <RelativeLayout\n            android:id=\"@+id/edittext_layout\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"3dp\"\n            android:layout_marginLeft=\"6dp\"\n            android:layout_marginRight=\"6dp\"\n            android:layout_weight=\"1\"\n            android:gravity=\"bottom\" >\n\n            <com.linkedin.android.spyglass.ui.MentionsEditText\n                android:id=\"@+id/et_sendmessage\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginRight=\"35dip\"\n                android:background=\"@null\"\n                android:maxLines=\"3\"\n                android:minHeight=\"40dp\"\n                android:paddingBottom=\"3dp\"\n                android:paddingLeft=\"1dp\"\n                android:paddingRight=\"1dp\"\n                android:paddingTop=\"3dp\"\n                android:textSize=\"18sp\" />\n\n            <RelativeLayout \n                android:id=\"@+id/rl_face\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_alignParentRight=\"true\"\n                android:layout_centerVertical=\"true\"\n                >\n            <ImageView\n                android:id=\"@+id/iv_face_normal\"\n                android:layout_width=\"24dip\"\n                android:layout_height=\"24dip\"\n                android:layout_margin=\"1dip\"\n                android:scaleType=\"fitCenter\"\n                android:src=\"@drawable/ease_chatting_biaoqing_btn_normal\"\n                android:visibility=\"visible\" />\n\n            <ImageView\n                android:id=\"@+id/iv_face_checked\"\n                android:layout_width=\"24dip\"\n                android:layout_height=\"24dip\"\n                android:layout_margin=\"1dip\"\n                android:scaleType=\"fitCenter\"\n                android:src=\"@drawable/ease_chatting_biaoqing_btn_enable\"\n                android:visibility=\"invisible\" />\n            \n            </RelativeLayout>\n        </RelativeLayout>\n\n        <Button\n            android:id=\"@+id/btn_more\"\n            android:layout_width=\"32dip\"\n            android:layout_height=\"32dip\"\n            android:layout_marginLeft=\"4dp\"\n            android:layout_marginRight=\"10dp\"\n            android:background=\"@drawable/ease_type_select_btn\" />\n\n        <Button\n            android:id=\"@+id/btn_send\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"32dp\"\n            android:layout_marginRight=\"4dp\"\n            android:background=\"@drawable/ease_chat_send_btn_selector\"\n            android:text=\"@string/button_send\"\n            android:textColor=\"#666666\"\n            android:textSize=\"16sp\"\n            android:visibility=\"gone\" />\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/ease_widget_emojicon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/ll_face_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"150dp\"\n    android:orientation=\"vertical\"\n    android:visibility=\"visible\" >\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/vPager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/emoticon_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/ll_emoticon\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingTop=\"8dp\"\n    android:paddingStart=\"16dp\"\n    android:paddingEnd=\"16dp\"\n    android:paddingBottom=\"8dp\"\n    android:orientation=\"vertical\"\n    android:visibility=\"visible\" >\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/vp_emoticon\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        />\n\n    <LinearLayout\n        android:id=\"@+id/ll_emoticon_indicator\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"20dp\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\">\n\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/gallery_activity_gallery.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#ff171b19\">\n\n    <com.beetle.bauhinia.gallery.view.ScrollViewPager\n        android:id=\"@+id/imagebrowser_svp_pager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n\n    <ImageButton\n        android:id=\"@+id/ib_view_more_picture\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginBottom=\"30dp\"\n        android:layout_marginRight=\"20dp\"\n        android:background=\"@drawable/gallery_watch_more_picture_background\"\n        android:contentDescription=\"@null\"\n        android:src=\"@drawable/gallery_watch_picture_preview_button_icon\"/>\n</RelativeLayout>\n"
  },
  {
    "path": "imlib/src/main/res/layout/gallery_activity_gallery_grid.xml",
    "content": "<RelativeLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <GridView\n        android:id=\"@+id/child_grid\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:cacheColorHint=\"@android:color/transparent\"\n        android:clipToPadding=\"false\"\n        android:fadingEdge=\"none\"\n        android:gravity=\"center\"\n        android:horizontalSpacing=\"2dp\"\n        android:numColumns=\"3\"\n        android:paddingRight=\"-1dp\"\n        android:scrollbars=\"none\"\n        android:verticalSpacing=\"2dp\">\n    </GridView>\n\n</RelativeLayout>\n"
  },
  {
    "path": "imlib/src/main/res/layout/gallery_activity_gallery_grid_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/framelayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:descendantFocusability=\"blocksDescendants\">\n\n    <ImageView\n        android:id=\"@+id/child_image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@drawable/gallery_picture_place_holder\"\n        android:scaleType=\"centerCrop\"/>\n\n</FrameLayout>\n"
  },
  {
    "path": "imlib/src/main/res/layout/gallery_select_dialog_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2011 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<!--\n  List item in the pop-up window that appears when tapping a contact's photo\n  in the contact editor. This is similar to the framework's select_dialog_item.xml layout\n  except the text appearance is medium.\n-->\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@android:id/text1\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:minHeight=\"?android:attr/listPreferredItemHeightSmall\"\n    android:textAppearance=\"?android:attr/textAppearanceMedium\"\n    android:textColor=\"?android:attr/textColorAlertDialogListItem\"\n    android:gravity=\"center_vertical\"\n    android:paddingLeft=\"14dip\"\n    android:paddingRight=\"15dip\"\n    android:paddingStart=\"14dip\"\n    android:paddingEnd=\"15dip\"\n    android:ellipsize=\"marquee\"/>\n"
  },
  {
    "path": "imlib/src/main/res/layout/item_emoticon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/rel_emoticon\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingBottom=\"14dp\"\n    android:paddingTop=\"14dp\">\n\n    <ImageView\n        android:id=\"@+id/iv_emoticon\"\n        android:layout_width=\"25dp\"\n        android:layout_height=\"25dp\"\n        android:layout_centerInParent=\"true\"\n        android:scaleType=\"fitCenter\" />\n\n</RelativeLayout>"
  },
  {
    "path": "imlib/src/main/res/layout/item_emoticon_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n    \n    <com.beetle.bauhinia.toolbar.EaseExpandRecylerView\n        android:id=\"@+id/rv_emoticon\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scrollbars=\"none\"\n        android:verticalSpacing=\"5dp\"\n        android:fadingEdge=\"none\"\n        />\n\n</RelativeLayout>\n"
  },
  {
    "path": "imlib/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <declare-styleable name=\"EmoticonPanel\">\n        <attr name=\"emoticonColumns\" format=\"integer\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"EaseChatExtendMenu\">\n        <attr name=\"numColumns\" format=\"integer\"/>\n    </declare-styleable>\n\n</resources>\n"
  },
  {
    "path": "imlib/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"black\">#000000</color>\n    <color name=\"red\">#FF0000</color>\n    <color name=\"gray\">#888888</color>\n    <color name=\"white\">#FFFFFF</color>\n\n    <color name=\"dot_enable_state\">#757575</color>\n    <color name=\"dot_disable_state\">#BDBDBD</color>\n</resources>"
  },
  {
    "path": "imlib/src/main/res/values/strings.xml",
    "content": "<resources>\n\n\n    <string name=\"gallery_image_saved_to\">Image saved to </string>\n    <string name=\"gallery_image_save_failed\">Image save failed!</string>\n    <string name=\"gallery_save_to_phone\">Save to phone</string>\n    <string name=\"gallery_chat_files\">Chat Files</string>\n\n    <string name=\"desc_emoticon_delete\">desc_emoticon_delete</string>\n    <string name=\"name_emoticon_delete\">name_emoticon_delete</string>\n\n</resources>\n"
  },
  {
    "path": "imlib/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\n    <string name=\"gallery_image_saved_to\">图片已保存至</string>\n    <string name=\"gallery_save_to_phone\">保存至手机</string>\n    <string name=\"gallery_image_save_failed\">图片保存失败</string>\n    <string name=\"gallery_chat_files\">聊天文件</string>\n\n\n    <string name=\"desc_emoticon_delete\">desc_emoticon_delete</string>\n    <string name=\"name_emoticon_delete\">name_emoticon_delete</string>\n\n\n\n</resources>"
  },
  {
    "path": "imsdk/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n    buildToolsVersion rootProject.ext.buildToolsVersion\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_6\n        targetCompatibility JavaVersion.VERSION_1_6\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    implementation project(':asynctcp')\n}\n"
  },
  {
    "path": "imsdk/src/androidTest/java/com/beetle/im/ApplicationTest.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}\n"
  },
  {
    "path": "imsdk/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.beetle.im\">\n\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n\n</manifest>\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/BytePacket.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\n\n/**\n * Created by houxh on 14-7-21.\n */\npublic class BytePacket {\n    static public void writeInt64(long v, byte[] dst, int pos) {\n        ByteBuffer b = ByteBuffer.allocate(8);\n        b.order(ByteOrder.BIG_ENDIAN);\n        b.putLong(v);\n        byte[] t = b.array();\n        System.arraycopy(t, 0, dst, pos, t.length);\n    }\n\n    static public void writeInt32(int v, byte[] dst, int pos) {\n        ByteBuffer b = ByteBuffer.allocate(4);\n        b.order(ByteOrder.BIG_ENDIAN);\n        b.putInt(v);\n        byte[] t = b.array();\n        System.arraycopy(t, 0, dst, pos, t.length);\n    }\n\n    static public void writeInt16(short v, byte[] dst, int pos) {\n        ByteBuffer b = ByteBuffer.allocate(2);\n        b.order(ByteOrder.BIG_ENDIAN);\n        b.putShort(v);\n        byte[] t = b.array();\n        System.arraycopy(t, 0, dst, pos, t.length);\n    }\n\n    static public long readInt64(byte[] bytes, int pos) {\n        ByteBuffer b = ByteBuffer.wrap(bytes, pos, 8);\n        b.order(ByteOrder.BIG_ENDIAN);\n        return b.getLong();\n    }\n\n    static public int readInt32(byte[] bytes, int pos) {\n        ByteBuffer b = ByteBuffer.wrap(bytes, pos, 4);\n        b.order(ByteOrder.BIG_ENDIAN);\n        return b.getInt();\n    }\n\n    static public short readInt16(byte[] bytes, int pos) {\n        ByteBuffer b = ByteBuffer.wrap(bytes, pos, 2);\n        b.order(ByteOrder.BIG_ENDIAN);\n        return b.getShort();\n    }\n\n    static public int packInetAddress(byte[] bytes) {\n        ByteBuffer b2 = ByteBuffer.wrap(bytes, 0, 4);\n        b2.order(ByteOrder.BIG_ENDIAN);\n        return b2.getInt();\n    }\n\n    static public byte[] unpackInetAddress(int iaddr) {\n        ByteBuffer b = ByteBuffer.allocate(4);\n        b.order(ByteOrder.BIG_ENDIAN);\n        b.putInt(iaddr);\n        byte[] t = b.array();\n        return t;\n    }\n\n    //little->net\n    static public int ltonl(int v) {\n        ByteBuffer b = ByteBuffer.allocate(4);\n        b.order(ByteOrder.LITTLE_ENDIAN);\n        b.putInt(v);\n        byte[] t = b.array();\n        ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);\n        b2.order(ByteOrder.BIG_ENDIAN);\n        return b2.getInt();\n    }\n    //net->little\n    static public int ntoll(int v) {\n        ByteBuffer b = ByteBuffer.allocate(4);\n        b.order(ByteOrder.BIG_ENDIAN);\n        b.putInt(v);\n        byte[] t = b.array();\n        ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);\n        b2.order(ByteOrder.LITTLE_ENDIAN);\n        return b2.getInt();\n    }\n\n    //little -> native\n    static public int ltohl(int v) {\n        ByteBuffer b = ByteBuffer.allocate(4);\n        b.order(ByteOrder.LITTLE_ENDIAN);\n        b.putInt(v);\n        byte[] t = b.array();\n        ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);\n        b2.order(ByteOrder.nativeOrder());\n        return b2.getInt();\n    }\n\n    //native -> little\n    static public int htoll(int v) {\n        ByteBuffer b = ByteBuffer.allocate(4);\n        b.order(ByteOrder.nativeOrder());\n        b.putInt(v);\n        byte[] t = b.array();\n        ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);\n        b2.order(ByteOrder.LITTLE_ENDIAN);\n        return b2.getInt();\n    }\n\n    static public int ntohl(int v) {\n        ByteBuffer b = ByteBuffer.allocate(4);\n        b.order(ByteOrder.nativeOrder());\n        b.putInt(v);\n        byte[] t = b.array();\n        ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);\n        b2.order(ByteOrder.BIG_ENDIAN);\n        return b2.getInt();\n    }\n\n    static public int htonl(int v) {\n        ByteBuffer b = ByteBuffer.allocate(4);\n        b.order(ByteOrder.BIG_ENDIAN);\n        b.putInt(v);\n        byte[] t = b.array();\n        ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);\n        b2.order(ByteOrder.nativeOrder());\n        return b2.getInt();\n    }\n\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/CustomerMessage.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 16/1/19.\n */\npublic class CustomerMessage extends IMMessage {\n    public long senderAppID;\n    public long receiverAppID;\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/CustomerMessageHandler.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 16/1/17.\n */\npublic interface CustomerMessageHandler {\n    public boolean handleMessage(CustomerMessage msg);\n    public boolean handleMessageACK(CustomerMessage msg);\n    public boolean handleMessageFailure(CustomerMessage msg);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/CustomerMessageObserver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 16/1/18.\n */\npublic interface CustomerMessageObserver {\n    public void onCustomerMessage(CustomerMessage msg);\n    public void onCustomerMessageACK(CustomerMessage msg);\n    public void onCustomerMessageFailure(CustomerMessage msg);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/GroupMessageHandler.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\nimport java.util.List;\n\n/**\n * Created by houxh on 15/3/21.\n */\npublic interface GroupMessageHandler {\n    public boolean handleMessages(List<IMMessage> msgs);\n    public boolean handleMessageACK(IMMessage msg, int error);\n    public boolean handleMessageFailure(IMMessage msg);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/GroupMessageObserver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n\nimport java.util.List;\n\n/**\n * Created by houxh on 14-7-23.\n */\npublic interface GroupMessageObserver {\n    public void onGroupMessages(List<IMMessage> msg);\n    public void onGroupMessageACK(IMMessage msg, int error);\n    public void onGroupMessageFailure(IMMessage msg);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/IMMessage.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 14-7-23.\n */\n\npublic class IMMessage {\n    public long msgLocalID;\n    public boolean secret;//点对点加密消息\n    public String plainContent;\n    public long sender;\n    public long receiver;\n    public int timestamp;\n    public String content;\n\n    ////避免在observer&handler中重复构造content对象\n    public Object contentObj;\n\n    //群组已读消息，通过点对点消息来发送\n    public long groupID;\n\n    //会话未读数减一\n    public boolean decrementUnread;\n\n    //是否由当前用户在当前设备所发出\n    public boolean isSelf;\n    //群组通知消息\n    public boolean isGroupNotification;\n}\n\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/IMService.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n\nimport android.app.AlarmManager;\nimport android.app.PendingIntent;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.net.ConnectivityManager;\nimport android.net.Network;\nimport android.net.NetworkCapabilities;\nimport android.net.NetworkInfo;\nimport android.net.NetworkRequest;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.PowerManager;\n\nimport com.beetle.AsyncSSLTCP;\nimport com.beetle.AsyncTCP;\nimport com.beetle.AsyncTCPInterface;\nimport com.beetle.TCPConnectCallback;\nimport com.beetle.TCPReadCallback;\nimport android.util.Log;\n\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport static android.os.SystemClock.uptimeMillis;\n\n/**\n * Created by houxh on 14-7-21.\n */\npublic class IMService {\n    private static final boolean ENABLE_SSL = false;\n    private static final String HOST = \"imnode2.gobelieve.io\";\n    private static int PORT;\n\n    {\n        if (ENABLE_SSL) {\n            PORT = 24430;\n        } else {\n            PORT = 23000;\n        }\n    }\n\n\n    private static final String TAG = \"imservice\";\n    private static final int HEARTBEAT = 60*3;\n    public static final String HEARTBEAT_ACTION = \"io.gobelieve.HEARTBEAT\";\n    private static final int CONNECT_TIMEOUT = 60;\n\n    public enum ConnectState {\n        STATE_UNCONNECTED,\n        STATE_CONNECTING,\n        STATE_CONNECTED,\n        STATE_CONNECTFAIL,\n    }\n\n    private AsyncTCPInterface tcp;\n    private boolean stopped = true;\n    private boolean suspended = true;\n    private boolean reachable = true;\n    private boolean isBackground = false;\n\n    private Timer testTimer;\n    private Timer connectTimer;\n    private Timer heartbeatTimer;\n    private int pingTimestamp;\n    private int connectTimestamp;//发起socket连接的时间戳\n    private int connectFailCount = 0;\n    private int seq = 0;\n    private ConnectState connectState = ConnectState.STATE_UNCONNECTED;\n\n    private String hostIP;\n    private int dnsTimestamp;\n\n    //set before call start\n    private String host;\n    private int port;\n    private String token;\n    private String deviceID;\n    private int connectTimeout;\n    private Looper looper;\n    private Handler handler;\n    private Handler mainThreadHandler;//调用observer\n\n    private boolean keepAlive;//应用在后台，保持socket连接\n    private PendingIntent alarmIntent;\n    private PowerManager.WakeLock wakeLock;\n\n    private long roomID;\n\n    //确保一个时刻只有一个同步过程在运行，以免收到重复的消息\n    private long syncKey;\n    //在同步过程中收到新的syncnotify消息\n    private long pendingSyncKey;\n    private boolean isSyncing;\n    private int syncTimestamp;\n\n    private static class GroupSync {\n        public long groupID;\n        public long syncKey;\n        //在同步过程中收到新的syncnotify消息\n        private long pendingSyncKey;\n        private boolean isSyncing;\n        private int syncTimestamp;\n    }\n\n    private HashMap<Long, GroupSync> groupSyncKeys = new HashMap<Long, GroupSync>();\n\n    SyncKeyHandler syncKeyHandler;\n    PeerMessageHandler peerMessageHandler;\n    GroupMessageHandler groupMessageHandler;\n    CustomerMessageHandler customerMessageHandler;\n    ArrayList<IMServiceObserver> observers = new ArrayList<IMServiceObserver>();\n    ArrayList<GroupMessageObserver> groupObservers = new ArrayList<GroupMessageObserver>();\n    ArrayList<PeerMessageObserver> peerObservers = new ArrayList<PeerMessageObserver>();\n    ArrayList<SystemMessageObserver> systemMessageObservers = new ArrayList<SystemMessageObserver>();\n    ArrayList<CustomerMessageObserver> customerServiceMessageObservers = new ArrayList<CustomerMessageObserver>();\n    ArrayList<RTMessageObserver> rtMessageObservers = new ArrayList<RTMessageObserver>();\n    ArrayList<RoomMessageObserver> roomMessageObservers = new ArrayList<RoomMessageObserver>();\n\n    ArrayList<Message> messages = new ArrayList<Message>();//已发出，等待ack的消息\n\n    ArrayList<IMMessage> receivedGroupMessages = new ArrayList<IMMessage>();\n\n    Message metaMessage;\n\n    private byte[] data;\n\n    private static IMService im = new IMService();\n\n    public static IMService getInstance() {\n        return im;\n    }\n\n    public IMService() {\n        this.host = HOST;\n        this.port = PORT;\n        this.connectTimeout = CONNECT_TIMEOUT;\n        this.setLooper(Looper.myLooper());\n        this.mainThreadHandler = new Handler(Looper.getMainLooper());\n    }\n\n    public ConnectState getConnectState() {\n        return connectState;\n    }\n\n    public void setLooper(Looper looper) {\n        this.looper = looper;\n        this.handler = new Handler(looper);\n    }\n\n    public Looper getLooper() {\n        return looper;\n    }\n\n    private void createTimer() {\n        if (connectTimer != null && heartbeatTimer != null) {\n            return;\n        }\n\n        connectTimer = new Timer(looper) {\n            @Override\n            protected void fire() {\n                IMService.this.connect();\n            }\n        };\n\n        heartbeatTimer = new Timer(looper) {\n            @Override\n            protected void fire() {\n                IMService.this.heartbeat();\n            }\n        };\n\n        testTimer = new Timer(looper) {\n            @Override\n            protected void fire() {\n                Log.i(TAG, \"test timer...\");\n            }\n        };\n    }\n\n    public void setConnectTimeout(int connectTimeout) {\n        this.connectTimeout = connectTimeout;\n    }\n    public void setHost(String host) {\n        this.host = host;\n        this.hostIP = \"\";\n    }\n    public void setToken(String token) {\n        this.token = token;\n    }\n\n    public void setDeviceID(String deviceID) {\n        this.deviceID = deviceID;\n    }\n\n    public void setKeepAlive(boolean keepAlive) {\n        this.keepAlive = keepAlive;\n    }\n\n    public void setWakeLock(PowerManager.WakeLock wl) {\n        this.wakeLock = wl;\n    }\n\n    public void setSyncKey(long syncKey) {\n        this.syncKey = syncKey;\n    }\n\n    public void addSuperGroupSyncKey(long groupID, long syncKey) {\n        GroupSync s = new GroupSync();\n        s.groupID = groupID;\n        s.syncKey = syncKey;\n        this.groupSyncKeys.put(groupID, s);\n        this.sendGroupSync(groupID, syncKey);\n    }\n\n    public void removeSuperGroupSyncKey(long groupID) {\n        this.groupSyncKeys.remove(groupID);\n    }\n\n    public void clearSuperGroupSyncKeys() {\n        this.groupSyncKeys.clear();\n    }\n\n    public void setSyncKeyHandler(SyncKeyHandler handler) {\n        this.syncKeyHandler = handler;\n    }\n\n    public void setPeerMessageHandler(PeerMessageHandler handler) {\n        this.peerMessageHandler = handler;\n    }\n    public void setGroupMessageHandler(GroupMessageHandler handler) {\n        this.groupMessageHandler = handler;\n    }\n    public void setCustomerMessageHandler(CustomerMessageHandler handler) {\n        this.customerMessageHandler = handler;\n    }\n\n    //call on main thread\n    public void addObserver(IMServiceObserver ob) {\n        if (observers.contains(ob)) {\n            return;\n        }\n        observers.add(ob);\n    }\n\n    public void removeObserver(IMServiceObserver ob) {\n        observers.remove(ob);\n    }\n\n\n    public void addPeerObserver(PeerMessageObserver ob) {\n        if (peerObservers.contains(ob)) {\n            return;\n        }\n        peerObservers.add(ob);\n    }\n\n    public void removePeerObserver(PeerMessageObserver ob) {\n        peerObservers.remove(ob);\n    }\n\n    public void addGroupObserver(GroupMessageObserver ob) {\n        if (groupObservers.contains(ob)) {\n            return;\n        }\n        groupObservers.add(ob);\n    }\n\n    public void removeGroupObserver(GroupMessageObserver ob) {\n        groupObservers.remove(ob);\n    }\n\n    public void addSystemObserver(SystemMessageObserver ob) {\n        if (systemMessageObservers.contains(ob)) {\n            return;\n        }\n        systemMessageObservers.add(ob);\n    }\n\n    public void removeSystemObserver(SystemMessageObserver ob) {\n        systemMessageObservers.remove(ob);\n    }\n\n    public void addCustomerServiceObserver(CustomerMessageObserver ob) {\n        if (customerServiceMessageObservers.contains(ob)) {\n            return;\n        }\n        customerServiceMessageObservers.add(ob);\n    }\n\n    public void removeCustomerServiceObserver(CustomerMessageObserver ob) {\n        customerServiceMessageObservers.remove(ob);\n    }\n\n    public void addRTObserver(RTMessageObserver ob) {\n        if (rtMessageObservers.contains(ob)) {\n            return;\n        }\n        rtMessageObservers.add(ob);\n    }\n\n    public void removeRTObserver(RTMessageObserver ob){\n        rtMessageObservers.remove(ob);\n    }\n\n    public void addRoomObserver(RoomMessageObserver ob) {\n        if (roomMessageObservers.contains(ob)) {\n            return;\n        }\n        roomMessageObservers.add(ob);\n    }\n\n    public void removeRoomObserver(RoomMessageObserver ob) {\n        roomMessageObservers.remove(ob);\n    }\n\n    public void enterBackground() {\n        runOnWorkerThread(new Runnable() {\n            @Override\n            public void run() {\n                IMService.this._enterBackground();\n            }\n        });\n    }\n\n    public void _enterBackground() {\n        Log.i(TAG, \"im service enter background\");\n        this.isBackground = true;\n        if (!this.stopped) {\n            suspend();\n            if (!keepAlive) {\n                IMService.this.connectState = ConnectState.STATE_UNCONNECTED;\n                IMService.this.publishConnectState();\n                this.close();\n            }\n        }\n    }\n\n    public void enterForeground() {\n        runOnWorkerThread(new Runnable() {\n            @Override\n            public void run() {\n                IMService.this._enterForeground();\n            }\n        });\n    }\n\n    public void _enterForeground() {\n        Log.i(TAG, \"im service enter foreground\");\n        this.isBackground = false;\n        if (!this.stopped) {\n            resume();\n        }\n    }\n\n    static boolean isNetworkConnected(NetworkCapabilities cap) {\n        return  cap != null && cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);\n    }\n\n    private static boolean isOnNet(Context context) {\n        if (null == context) {\n            Log.e(\"\", \"context is null\");\n            return false;\n        }\n        boolean isOnNet = false;\n        ConnectivityManager connectivityManager = (ConnectivityManager) context\n                .getSystemService(Context.CONNECTIVITY_SERVICE);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            Network network = connectivityManager.getActiveNetwork();\n            NetworkCapabilities cap = connectivityManager.getNetworkCapabilities(network);\n            return isNetworkConnected(cap);\n        } else {\n            NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();\n            if (null != activeNetInfo) {\n                isOnNet = activeNetInfo.isConnected();\n                Log.i(TAG, \"active net info:\" + activeNetInfo);\n            }\n            return isOnNet;\n        }\n    }\n\n    static class NetworkCallback extends ConnectivityManager.NetworkCallback {\n        private Context context;\n        public NetworkCallback(Context context) {\n            this.context = context;\n        }\n\n        @Override\n        public void onAvailable(Network network) {\n            Log.i(TAG, \"on network available:\" + network.toString());\n            IMService.im.handler.post(new Runnable() {\n                @Override\n                public void run() {\n                    IMService.im.reachable = true;\n                    if (!IMService.im.stopped && !IMService.im.isBackground) {\n                        //todo 优化 可以判断当前连接的socket的localip和当前网络的ip是一样的情况下\n                        //就没有必要重连socket\n                        Log.i(TAG, \"reconnect im service\");\n                        IMService.im.suspend();\n                        IMService.im.connectState = ConnectState.STATE_UNCONNECTED;\n                        IMService.im.publishConnectState();\n                        IMService.im.close();\n                        IMService.im.resume();\n                    }\n                }\n            });\n        }\n\n        @Override\n        public void onLost(Network network) {\n            boolean netAvaiable = isOnNet(context);\n            Log.i(TAG, \"on network lost:\" + network.toString());\n            Log.i(TAG, \"active network status:\" + netAvaiable);\n\n            if (netAvaiable) {\n                IMService.im.handler.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        IMService.im.reachable = true;\n                        if (!IMService.im.stopped && !IMService.im.isBackground) {\n                            //todo 优化 可以判断当前连接的socket的localip和当前网络的ip是一样的情况下\n                            //就没有必要重连socket\n                            Log.i(TAG, \"reconnect im service\");\n                            IMService.im.suspend();\n                            IMService.im.connectState = ConnectState.STATE_UNCONNECTED;\n                            IMService.im.publishConnectState();\n                            IMService.im.close();\n                            IMService.im.resume();\n                        }\n                    }\n                });\n            } else {\n                IMService.im.handler.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        IMService.im.reachable = false;\n                        if (!IMService.im.stopped) {\n                            IMService.im.suspend();\n                            IMService.im.connectState = ConnectState.STATE_UNCONNECTED;\n                            IMService.im.publishConnectState();\n                            IMService.im.close();\n                        }\n                    }\n                });\n            }\n        }\n    }\n\n    static class HeartbeatReceiver extends BroadcastReceiver {\n        private final String TAG = \"imservice\";\n\n        @Override\n        public void onReceive (Context context, Intent intent) {\n            String action = intent.getAction();\n            Log.i(TAG, \"broadcast receive action:\" + action);\n            if (action.equals(IMService.HEARTBEAT_ACTION)) {\n                if (!IMService.im.keepAlive) {\n                    Log.w(TAG, \"not keepalive, dummy alarm heatbeat action\");\n                    return;\n                }\n                if (!IMService.im.isBackground) {\n                    Log.w(TAG, \"not in background, dummy alarm heatbeat action\");\n                    return;\n                }\n\n                if (IMService.im.wakeLock != null) {\n                    IMService.im.wakeLock.acquire(1000);\n                }\n\n                IMService.im.heartbeat();\n            }\n        }\n    };\n\n    public void registerConnectivityChangeReceiver(Context context) {\n        ConnectivityManager connectivityManager = (ConnectivityManager) context\n                .getSystemService(Context.CONNECTIVITY_SERVICE);\n        NetworkCallback cb = new NetworkCallback(context);\n        NetworkRequest.Builder builder = new NetworkRequest.Builder();\n        NetworkRequest request = builder.build();\n        connectivityManager.registerNetworkCallback(request, cb);\n        this.reachable = isOnNet(context);\n        Log.i(TAG, \"network reachable:\" + this.reachable);\n\n        HeartbeatReceiver receiver = new HeartbeatReceiver();\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(HEARTBEAT_ACTION);\n        context.registerReceiver(receiver, filter, null, handler);\n    }\n\n    //设置了keepalive之后需要创建系统级的定时器\n    public void startAlarm(Context context) {\n        if (!keepAlive) {\n            Log.w(TAG, \"keepalive false, can't start alarm\");\n            return;\n        }\n\n        Log.i(TAG, \"start alarm\");\n        Context appContext = context;\n        final int ALARM_INTERVAL = HEARTBEAT*1000;//3 * 60 * 1000;\n        AlarmManager alarmMgr;\n        PendingIntent alarmIntent;\n        alarmMgr = (AlarmManager) appContext.getSystemService(Context.ALARM_SERVICE);\n        Intent intent = new Intent();\n        intent.setAction(HEARTBEAT_ACTION);\n        intent.setPackage(context.getPackageName());\n        alarmIntent = PendingIntent.getBroadcast(appContext, 999, intent, 0);\n        Calendar calendar = Calendar.getInstance();\n\n        //从doze模式恢复后，此alarm会失效\n        alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),\n                ALARM_INTERVAL, alarmIntent);\n\n        this.alarmIntent = alarmIntent;\n    }\n\n    public void stopAlarm(Context context) {\n        if (alarmIntent != null) {\n            Log.i(TAG, \"stop alarm\");\n            AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);\n            alarmManager.cancel(alarmIntent);\n            alarmIntent = null;\n        }\n    }\n\n    public void start() {\n        runOnWorkerThread(new Runnable() {\n            @Override\n            public void run() {\n                IMService.this._start();\n            }\n        });\n    }\n\n    private void _start() {\n        if (this.token.length() == 0) {\n            throw new RuntimeException(\"NO TOKEN PROVIDED\");\n        }\n\n        if (!this.stopped) {\n            Log.i(TAG, \"already started\");\n            return;\n        }\n        Log.i(TAG, \"start im service\");\n        this.stopped = false;\n        createTimer();\n        this.resume();\n\n        testTimer.setTimer(uptimeMillis(), HEARTBEAT*1000);\n        testTimer.resume();\n\n        //应用在后台的情况下基本不太可能调用start\n        if (this.isBackground) {\n            Log.w(TAG, \"start im service when app is background\");\n        }\n    }\n\n    public void stop() {\n        runOnWorkerThread(new Runnable() {\n            @Override\n            public void run() {\n                IMService.this._stop();\n            }\n        });\n    }\n\n    private void _stop() {\n        if (this.stopped) {\n            Log.i(TAG, \"already stopped\");\n            return;\n        }\n        Log.i(TAG, \"stop im service\");\n        stopped = true;\n        suspend();\n\n        testTimer.suspend();\n\n        IMService.this.connectState = ConnectState.STATE_UNCONNECTED;\n        IMService.this.publishConnectState();\n        this.close();\n        if (this.isBackground) {\n            Log.w(TAG, \"stop im service when app is background\");\n        }\n    }\n\n    private void suspend() {\n        if (this.suspended) {\n            Log.i(TAG, \"suspended\");\n            return;\n        }\n\n        heartbeatTimer.suspend();\n        connectTimer.suspend();\n        this.suspended = true;\n\n        Log.i(TAG, \"suspend im service\");\n    }\n\n    private void resume() {\n        if (!this.suspended) {\n            return;\n        }\n        Log.i(TAG, \"resume im service\");\n        this.suspended = false;\n\n        connectTimer.setTimer(uptimeMillis());\n        connectTimer.resume();\n\n        heartbeatTimer.setTimer(uptimeMillis(), HEARTBEAT*1000);\n        heartbeatTimer.resume();\n\n        refreshHost();\n    }\n\n    public void sendPeerMessageAsync(final IMMessage im) {\n        handler.post(new Runnable() {\n            @Override\n            public void run() {\n                boolean r = IMService.this.sendPeerMessage(im);\n                if (!r) {\n                    if (peerMessageHandler != null) {\n                        peerMessageHandler.handleMessageFailure(im);\n                    }\n                    publishPeerMessageFailure(im);\n                }\n            }\n        });\n    }\n\n    public boolean sendPeerMessage(IMMessage im) {\n        assertLooper();\n        Message msg = new Message();\n        msg.cmd = Command.MSG_IM;\n        msg.body = im;\n        if (sendMessage(msg)) {\n            messages.add(msg);\n            //在发送需要回执的消息时尽快发现socket已经断开的情况\n            sendHeartbeat();\n            return true;\n        } else if (!suspended) {\n            msg.failCount = 1;\n            messages.add(msg);\n            return true;\n        } else {\n            return  false;\n        }\n\n    }\n\n    public void sendGroupMessageAsync(final IMMessage im) {\n        handler.post(new Runnable() {\n            @Override\n            public void run() {\n                boolean r = IMService.this.sendGroupMessage(im);\n                if (!r) {\n                    if (groupMessageHandler != null) {\n                        groupMessageHandler.handleMessageFailure(im);\n                    }\n                    publishGroupMessageFailure(im);\n                }\n            }\n        });\n    }\n\n    public boolean sendGroupMessage(IMMessage im) {\n        assertLooper();\n        Message msg = new Message();\n        msg.cmd = Command.MSG_GROUP_IM;\n        msg.body = im;\n        if (sendMessage(msg)) {\n            messages.add(msg);\n            //在发送需要回执的消息时尽快发现socket已经断开的情况\n            sendHeartbeat();\n\n            return true;\n        } else if (!suspended) {\n            msg.failCount = 1;\n            messages.add(msg);\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    public void sendCustomerMessageAsync(final CustomerMessage im) {\n        handler.post(new Runnable() {\n            @Override\n            public void run() {\n                boolean r = IMService.this.sendCustomerMessage(im);\n                if (!r) {\n                    if (customerMessageHandler != null) {\n                        customerMessageHandler.handleMessageFailure(im);\n                    }\n                    publishCustomerServiceMessageACK(im);\n                }\n            }\n        });\n    }\n\n\n    public boolean sendCustomerMessage(CustomerMessage im) {\n        assertLooper();\n        Message msg = new Message();\n        msg.cmd = Command.MSG_CUSTOMER;\n        msg.body = im;\n        if (sendMessage(msg)) {\n            messages.add(msg);\n            //在发送需要回执的消息时尽快发现socket已经断开的情况\n            sendHeartbeat();\n\n            return true;\n        } else if (!suspended) {\n            msg.failCount = 1;\n            messages.add(msg);\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    public void sendRTMessageAsync(final RTMessage rt) {\n        handler.post(new Runnable() {\n            @Override\n            public void run() {\n                IMService.this.sendRTMessage(rt);\n\n            }\n        });\n    }\n\n    public boolean sendRTMessage(RTMessage rt) {\n        assertLooper();\n        Message msg = new Message();\n        msg.cmd = Command.MSG_RT;\n        msg.body = rt;\n        if (!sendMessage(msg)) {\n            return false;\n        }\n        return true;\n    }\n\n    public void sendRoomMessageAsync(final RoomMessage rm) {\n        handler.post(new Runnable() {\n            @Override\n            public void run() {\n                IMService.this.sendRoomMessage(rm);\n            }\n        });\n    }\n\n    public boolean sendRoomMessage(RoomMessage rm) {\n        assertLooper();\n        Message msg = new Message();\n        msg.cmd = Command.MSG_ROOM_IM;\n        msg.body = rm;\n        return sendMessage(msg);\n    }\n\n    private void sendEnterRoom(long roomID) {\n        Message msg = new Message();\n        msg.cmd = Command.MSG_ENTER_ROOM;\n        msg.body = new Long(roomID);\n        sendMessage(msg);\n    }\n\n    private void sendLeaveRoom(long roomID) {\n        Message msg = new Message();\n        msg.cmd = Command.MSG_LEAVE_ROOM;\n        msg.body = new Long(roomID);\n        sendMessage(msg);\n    }\n\n\n    public void enterRoom(final long roomID) {\n        if (roomID == 0) {\n            return;\n        }\n\n        runOnWorkerThread(new Runnable() {\n            @Override\n            public void run() {\n                IMService.this.roomID = roomID;\n                sendEnterRoom(roomID);\n            }\n        });\n    }\n\n    public void leaveRoom(final long roomID) {\n        if (roomID == 0) {\n            return;\n        }\n        runOnWorkerThread(new Runnable() {\n            @Override\n            public void run() {\n                if (IMService.this.roomID != roomID) {\n                    return;\n                }\n                sendLeaveRoom(roomID);\n                IMService.this.roomID = 0;\n            }\n        });\n    }\n\n    private void close() {\n        if (receivedGroupMessages.size() > 0) {\n            Log.i(TAG, \"socket closed, received group messages:\" + receivedGroupMessages);\n            receivedGroupMessages.clear();\n        }\n\n        if (metaMessage != null) {\n            Log.i(TAG, \"socket closed, meta message:\" + metaMessage);\n            metaMessage = null;\n        }\n\n        ArrayList<IMMessage> peerMessages = new ArrayList<IMMessage>();\n        ArrayList<IMMessage> groupMessages = new ArrayList<IMMessage>();\n        ArrayList<CustomerMessage> customerMessages = new ArrayList<CustomerMessage>();\n        ArrayList<Message> resendMessages = new ArrayList<Message>();\n        for (int i = 0; i < messages.size(); i++) {\n            Message m = messages.get(i);\n            //消息只会被重发一次\n            if (m.failCount > 0 || suspended) {\n                if (m.cmd == Command.MSG_IM) {\n                    peerMessages.add((IMMessage)m.body);\n                } else if (m.cmd == Command.MSG_GROUP_IM) {\n                    groupMessages.add((IMMessage)m.body);\n                } else if (m.cmd == Command.MSG_CUSTOMER) {\n                    customerMessages.add((CustomerMessage)m.body);\n                }\n            } else {\n                m.failCount += 1;\n                resendMessages.add(m);\n            }\n        }\n\n        for (int i = 0; i < peerMessages.size(); i++) {\n            IMMessage im = peerMessages.get(i);\n            if (peerMessageHandler != null) {\n                peerMessageHandler.handleMessageFailure(im);\n            }\n            publishPeerMessageFailure(im);\n        }\n        for (int i = 0; i < groupMessages.size(); i++) {\n            IMMessage im = groupMessages.get(i);\n            if (groupMessageHandler != null) {\n                groupMessageHandler.handleMessageFailure(im);\n            }\n            publishGroupMessageFailure(im);\n        }\n\n        for (int i= 0; i < customerMessages.size(); i++) {\n            CustomerMessage im = customerMessages.get(i);\n            if (customerMessageHandler != null) {\n                customerMessageHandler.handleMessageFailure(im);\n            }\n            publishCustomerServiceMessageFailure(im);\n        }\n\n        messages = resendMessages;\n\n        if (this.tcp != null) {\n            Log.i(TAG, \"close tcp\");\n            this.tcp.close();\n            final AsyncTCPInterface local_tcp = this.tcp;\n            this.tcp = null;\n            this.handler.post(new Runnable() {\n                @Override\n                public void run() {\n                    local_tcp.release();\n                }\n            });\n        }\n    }\n\n    private static int now() {\n        Date date = new Date();\n        long t = date.getTime();\n        return (int)(t/1000);\n    }\n\n    private void refreshHost() {\n        new AsyncTask<Void, Integer, String>() {\n            @Override\n            protected String doInBackground(Void... urls) {\n                return lookupHost(IMService.this.host);\n            }\n\n            private String lookupHost(String host) {\n                try {\n                    InetAddress[] inetAddresses = InetAddress.getAllByName(host);\n                    for (int i = 0; i < inetAddresses.length; i++) {\n                        InetAddress inetAddress = inetAddresses[i];\n                        Log.i(TAG, \"host address:\" + inetAddress.getHostAddress());\n                        if (inetAddress instanceof Inet4Address) {\n                            return inetAddress.getHostAddress();\n                        }\n                    }\n                    return \"\";\n                } catch (UnknownHostException exception) {\n                    exception.printStackTrace();\n                    return \"\";\n                }\n            }\n\n            @Override\n            protected void onPostExecute(String result) {\n                if (result.length() > 0) {\n                    IMService.this.hostIP = result;\n                    IMService.this.dnsTimestamp = now();\n                }\n            }\n        }.execute();\n    }\n\n    private void startConnectTimer() {\n        if (this.stopped || this.suspended || this.isBackground) {\n            return;\n        }\n        long t;\n        if (this.connectFailCount > 60) {\n            t = uptimeMillis() + 60*1000;\n        } else {\n            t = uptimeMillis() + this.connectFailCount*1000;\n        }\n        connectTimer.setTimer(t);\n        Log.d(TAG, \"start connect timer:\" + this.connectFailCount);\n    }\n\n    private void onConnected() {\n        Log.i(TAG, \"tcp connected\");\n\n        int now = now();\n        this.data = null;\n        this.connectFailCount = 0;\n        this.connectState = ConnectState.STATE_CONNECTED;\n        this.publishConnectState();\n        this.sendAuth();\n        if (this.roomID > 0) {\n            this.sendEnterRoom(this.roomID);\n        }\n        this.sendSync(this.syncKey);\n        this.isSyncing = true;\n        this.syncTimestamp = now;\n        this.pendingSyncKey = 0;\n        for (Map.Entry<Long, GroupSync> e : this.groupSyncKeys.entrySet()) {\n            GroupSync s = e.getValue();\n            this.sendGroupSync(e.getKey(), s.syncKey);\n            s.isSyncing = true;\n            s.syncTimestamp = now;\n            s.pendingSyncKey = 0;\n        }\n        //重发失败的消息\n        for (Message m : messages) {\n            this.sendMessage(m);\n        }\n\n        this.tcp.startRead();\n    }\n\n    private void connect() {\n        if (this.tcp != null) {\n            return;\n        }\n        if (this.stopped) {\n            Log.e(TAG, \"opps....\");\n            return;\n        }\n\n        if (hostIP == null || hostIP.length() == 0) {\n            refreshHost();\n            IMService.this.connectFailCount++;\n            Log.i(TAG, \"host ip is't resolved\");\n\n            long t;\n            if (this.connectFailCount > 60) {\n                t = uptimeMillis() + 60*1000;\n            } else {\n                t = uptimeMillis() + this.connectFailCount*1000;\n            }\n            connectTimer.setTimer(t);\n            return;\n        }\n\n        if (now() - dnsTimestamp > 5*60) {\n            refreshHost();\n        }\n\n        this.pingTimestamp = 0;\n        this.connectTimestamp = now();\n        this.connectState = ConnectState.STATE_CONNECTING;\n        IMService.this.publishConnectState();\n        if (ENABLE_SSL) {\n            this.tcp = new AsyncSSLTCP();\n        } else {\n            this.tcp = new AsyncTCP();\n        }\n        Log.i(TAG, \"new tcp...\");\n\n        this.tcp.setConnectCallback(new TCPConnectCallback() {\n            @Override\n            public void onConnect(Object tcp, int status) {\n                if (status != 0) {\n                    Log.i(TAG, \"connect err:\" + status);\n                    IMService.this.connectFailCount++;\n                    IMService.this.connectState = ConnectState.STATE_CONNECTFAIL;\n                    IMService.this.publishConnectState();\n                    IMService.this.close();\n                    IMService.this.startConnectTimer();\n                } else {\n                    IMService.this.onConnected();\n                }\n            }\n        });\n\n        this.tcp.setReadCallback(new TCPReadCallback() {\n            @Override\n            public void onRead(Object tcp, byte[] data) {\n                if (data.length == 0) {\n                    Log.i(TAG, \"tcp read eof\");\n                    IMService.this.connectState = ConnectState.STATE_UNCONNECTED;\n                    IMService.this.publishConnectState();\n                    IMService.this.handleClose();\n                } else {\n                    IMService.this.pingTimestamp = 0;\n                    boolean b = IMService.this.handleData(data);\n                    if (!b) {\n                        IMService.this.connectState = ConnectState.STATE_UNCONNECTED;\n                        IMService.this.publishConnectState();\n                        IMService.this.handleClose();\n                    }\n                }\n            }\n        });\n\n        boolean r = this.tcp.connect(this.hostIP, this.port);\n        Log.i(TAG, \"tcp connect:\" + r);\n        if (!r) {\n            this.tcp = null;\n            IMService.this.connectFailCount++;\n            IMService.this.connectState = ConnectState.STATE_CONNECTFAIL;\n            publishConnectState();\n            startConnectTimer();\n        } else if (connectTimeout > 0){\n            Timer t = new Timer() {\n                @Override\n                protected void fire() {\n                    int now = now();\n                    if (IMService.this.connectState == ConnectState.STATE_CONNECTING &&\n                            now - IMService.this.connectTimestamp >= connectTimeout) {\n                        //connect timeout\n                        Log.i(TAG, \"connect timeout\");\n                        IMService.this.connectFailCount++;\n                        IMService.this.connectState = ConnectState.STATE_CONNECTFAIL;\n                        IMService.this.publishConnectState();\n                        IMService.this.close();\n                        IMService.this.startConnectTimer();\n                    }\n                }\n            };\n\n            t.setTimer(uptimeMillis()+1000*connectTimeout+100);\n            t.resume();\n        }\n    }\n\n    private void heartbeat() {\n        IMService.ConnectState state = connectState;\n        Log.i(TAG, \"heartbeat, im connect state:\" + state);\n        if (state == IMService.ConnectState.STATE_CONNECTFAIL || state == IMService.ConnectState.STATE_UNCONNECTED) {\n            Log.i(TAG, \"connect im service\");\n            connect();\n        } else if (state == IMService.ConnectState.STATE_CONNECTED) {\n            Log.i(TAG, \"send heartbeat\");\n            sendHeartbeat();\n        } else if (state == IMService.ConnectState.STATE_CONNECTING) {\n            int t = connectTimestamp;\n            int n = now();\n            //90s timeout\n            if (n - t > 90) {\n                Log.i(TAG, \"im service connect timeout, reconnect\");\n                close();\n                connect();\n            }\n        }\n    }\n\n    private void handleAuthStatus(Message msg) {\n        Integer status = (Integer)msg.body;\n        Log.d(TAG, \"auth status:\" + status);\n        if (status != 0) {\n            //失效的accesstoken,2s后重新连接\n            this.connectFailCount = 2;\n            this.connectState = ConnectState.STATE_UNCONNECTED;\n            this.publishConnectState();\n            this.close();\n            this.startConnectTimer();\n        }\n    }\n\n    private void handleIMMessage(Message msg) {\n        IMMessage im = (IMMessage)msg.body;\n        Log.d(TAG, \"im message sender:\" + im.sender + \" receiver:\" + im.receiver + \" content:\" + im.content);\n\n        im.isSelf = (msg.flag & Flag.MESSAGE_FLAG_SELF) != 0;\n        if (peerMessageHandler != null && !peerMessageHandler.handleMessage(im)) {\n            Log.i(TAG, \"handle im message fail\");\n            return;\n        }\n        if (im.groupID > 0) {\n            im.receiver = im.groupID;\n            if ((msg.flag & Flag.MESSAGE_FLAG_PUSH) != 0) {\n                ArrayList<IMMessage> array = new ArrayList<IMMessage>();\n                array.add(im);\n                if (groupMessageHandler != null && !groupMessageHandler.handleMessages(array)) {\n                    Log.i(TAG, \"handle group messages fail\");\n                    return;\n                }\n                publishGroupMessages(array);\n            } else {\n                receivedGroupMessages.add(im);\n            }\n        } else if (im.secret) {\n            publishPeerSecretMessage(im);\n        } else {\n            publishPeerMessage(im);\n        }\n        sendACK(msg.seq);\n    }\n\n    private void handleGroupIMMessage(Message msg) {\n        IMMessage im = (IMMessage)msg.body;\n        Log.d(TAG, \"group im message sender:\" + im.sender + \" receiver:\" + im.receiver + \" content:\" + im.content);\n\n        im.isSelf = (msg.flag & Flag.MESSAGE_FLAG_SELF) != 0;\n\n        if ((msg.flag & Flag.MESSAGE_FLAG_PUSH) != 0) {\n            ArrayList<IMMessage> array = new ArrayList<IMMessage>();\n            array.add(im);\n            if (groupMessageHandler != null && !groupMessageHandler.handleMessages(array)) {\n                Log.i(TAG, \"handle group messages fail\");\n                return;\n            }\n            publishGroupMessages(array);\n        } else {\n            receivedGroupMessages.add(im);\n        }\n        sendACK(msg.seq);\n    }\n\n    private void handleGroupNotification(Message msg) {\n        String notification = (String)msg.body;\n        Log.d(TAG, \"group notification:\" + notification);\n\n        if ((msg.flag & Flag.MESSAGE_FLAG_PUSH) != 0) {\n            ArrayList<IMMessage> array = new ArrayList<IMMessage>();\n            IMMessage im = new IMMessage();\n            im.content = notification;\n            im.isGroupNotification = true;\n            array.add(im);\n            if (groupMessageHandler != null && !groupMessageHandler.handleMessages(array)) {\n                Log.i(TAG, \"handle group messages fail\");\n                return;\n            }\n            publishGroupMessages(array);\n        } else {\n            IMMessage im = new IMMessage();\n            im.content = notification;\n            im.isGroupNotification = true;\n            receivedGroupMessages.add(im);\n        }\n\n        sendACK(msg.seq);\n    }\n\n    private void handleClose() {\n        close();\n        startConnectTimer();\n    }\n\n    private void handleACK(Message msg) {\n        MessageACK ack = (MessageACK)msg.body;\n        Integer seq = ack.seq;\n\n        int index = -1;\n        for (int i = 0; i < messages.size(); i++) {\n            Message m = messages.get(i);\n            if (m.seq == seq) {\n                index = i;\n                break;\n            }\n        }\n\n        if (index == -1) {\n            return;\n        }\n\n        Message m = messages.get(index);\n        messages.remove(index);\n        IMMessage im = null;\n        IMMessage groupMsg = null;\n        CustomerMessage cm = null;\n        if (m.cmd == Command.MSG_IM) {\n            im = (IMMessage)m.body;\n        } else if (m.cmd == Command.MSG_GROUP_IM) {\n            groupMsg = (IMMessage)m.body;\n        } else if (m.cmd == Command.MSG_CUSTOMER) {\n            cm = (CustomerMessage)m.body;\n        }\n\n        if (im != null) {\n            if (peerMessageHandler != null && !peerMessageHandler.handleMessageACK(im, ack.status)) {\n                Log.w(TAG, \"handle message ack fail\");\n                return;\n            }\n            publishPeerMessageACK(im, ack.status);\n        }\n\n        if (groupMsg != null) {\n            if (groupMessageHandler != null && !groupMessageHandler.handleMessageACK(groupMsg, ack.status)) {\n                Log.i(TAG, \"handle group message ack fail\");\n                return;\n            }\n            publishGroupMessageACK(groupMsg, ack.status);\n        }\n\n        if (cm != null) {\n            if (customerMessageHandler != null && !customerMessageHandler.handleMessageACK(cm)) {\n                Log.i(TAG, \"handle customer service message ack fail\");\n                return;\n            }\n            publishCustomerServiceMessageACK(cm);\n        }\n\n        Message metaMessage = this.metaMessage;\n        this.metaMessage = null;\n        if (metaMessage != null && metaMessage.seq + 1 == msg.seq) {\n            Metadata metadata = (Metadata)metaMessage.body;\n            if (metadata.prevSyncKey == 0 || metadata.syncKey == 0) {\n                return;\n            }\n\n            long newSyncKey = metadata.syncKey;\n            if ((msg.flag & Flag.MESSAGE_FLAG_SUPER_GROUP) != 0) {\n                if (groupMsg == null) {\n                    return;\n                }\n\n                long groupID = groupMsg.receiver;\n                GroupSync s = null;\n                if (this.groupSyncKeys.containsKey(groupID)) {\n                    s = this.groupSyncKeys.get(groupID);\n                } else {\n                    return;\n                }\n\n                if (s.syncKey == metadata.prevSyncKey && newSyncKey != s.syncKey) {\n                    s.syncKey = newSyncKey;\n                    if (this.syncKeyHandler != null) {\n                        this.syncKeyHandler.saveGroupSyncKey(groupID, s.syncKey);\n                        this.sendGroupSyncKey(groupID, s.syncKey);\n                    }\n                }\n            } else {\n                if (this.syncKey == metadata.prevSyncKey && newSyncKey != this.syncKey) {\n                    this.syncKey = newSyncKey;\n                    if (this.syncKeyHandler != null) {\n                        this.syncKeyHandler.saveSyncKey(this.syncKey);\n                        this.sendSyncKey(this.syncKey);\n                    }\n                }\n            }\n        }\n    }\n\n\n    private void handleSystemMessage(Message msg) {\n        String sys = (String)msg.body;\n        for (int i = 0; i < systemMessageObservers.size(); i++ ) {\n            SystemMessageObserver ob = systemMessageObservers.get(i);\n            ob.onSystemMessage(sys);\n        }\n\n        sendACK(msg.seq);\n    }\n\n    private void handleCustomerMessage(Message msg) {\n        CustomerMessage cs = (CustomerMessage)msg.body;\n\n        cs.isSelf = (msg.flag & Flag.MESSAGE_FLAG_SELF) != 0;\n        if (customerMessageHandler != null && !customerMessageHandler.handleMessage(cs)) {\n            Log.i(TAG, \"handle customer service message fail\");\n            return;\n        }\n\n        publishCustomerMessage(cs);\n\n        sendACK(msg.seq);\n    }\n\n    private void handleRTMessage(Message msg) {\n        final RTMessage rt = (RTMessage)msg.body;\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < rtMessageObservers.size(); i++ ) {\n                    RTMessageObserver ob = rtMessageObservers.get(i);\n                    ob.onRTMessage(rt);\n                }\n            }\n        });\n    }\n\n    private void handleRoomMessage(Message msg) {\n        final RoomMessage rm = (RoomMessage)msg.body;\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i= 0; i < roomMessageObservers.size(); i++) {\n                    RoomMessageObserver ob = roomMessageObservers.get(i);\n                    ob.onRoomMessage(rm);\n                }\n            }\n        });\n    }\n\n    private void handleSyncNotify(Message msg) {\n        SyncNotify notify = (SyncNotify)msg.body;\n        Long newSyncKey = notify.syncKey;\n        Log.i(TAG, \"sync notify:\" + newSyncKey);\n\n        int now = now();\n\n        //4s同步超时\n        boolean isSyncing = this.isSyncing && (now - this.syncTimestamp < 4);\n\n        if (!isSyncing && newSyncKey > this.syncKey) {\n            sendSync(this.syncKey);\n            this.isSyncing = true;\n            this.syncTimestamp = now;\n        } else if (newSyncKey > this.pendingSyncKey) {\n            //等待此次同步结束后，再同步\n            this.pendingSyncKey = newSyncKey;\n        }\n    }\n\n    private void handleSyncBegin(Message msg) {\n        Log.i(TAG, \"sync begin...:\" + msg.body);\n    }\n\n    private void handleSyncEnd(Message msg) {\n        Log.i(TAG, \"sync end...:\" + msg.body);\n        if (receivedGroupMessages.size() > 0) {\n            if (groupMessageHandler != null && !groupMessageHandler.handleMessages(receivedGroupMessages)) {\n                Log.i(TAG, \"handle group messages fail\");\n                return;\n            }\n            publishGroupMessages(receivedGroupMessages);\n            receivedGroupMessages.clear();\n        }\n\n        Long newSyncKey = (Long)msg.body;\n        if (newSyncKey != this.syncKey) {\n            this.syncKey = newSyncKey;\n            if (this.syncKeyHandler != null) {\n                this.syncKeyHandler.saveSyncKey(this.syncKey);\n                this.sendSyncKey(this.syncKey);\n            }\n        }\n\n        int now = now();\n        this.isSyncing = false;\n        if (this.pendingSyncKey > this.syncKey) {\n            //上次同步过程中，再次收到了新的SyncGroupNotify消息\n            this.sendSync(this.syncKey);\n            this.isSyncing = true;\n            this.syncTimestamp = now;\n            this.pendingSyncKey = 0;\n        }\n    }\n\n    private void handleSyncGroupNotify(Message msg) {\n        GroupSyncNotify key = (GroupSyncNotify)msg.body;\n        Log.i(TAG, \"group sync notify:\" + key.groupID + \" \" + key.syncKey);\n\n        GroupSync s = null;\n        if (this.groupSyncKeys.containsKey(key.groupID)) {\n            s = this.groupSyncKeys.get(key.groupID);\n        } else {\n            //接受到新加入的超级群消息\n            s = new GroupSync();\n            s.groupID = key.groupID;\n            s.syncKey = 0;\n            this.groupSyncKeys.put(new Long(key.groupID), s);\n        }\n\n        int now = now();\n        //4s同步超时\n        boolean isSyncing = s.isSyncing && (now - s.syncTimestamp < 4);\n        if (!isSyncing && key.syncKey > s.syncKey) {\n            this.sendGroupSync(key.groupID, s.syncKey);\n            s.isSyncing = true;\n            s.syncTimestamp = now;\n        } else if (key.syncKey > s.pendingSyncKey) {\n            s.pendingSyncKey = key.syncKey;\n        }\n    }\n\n    private void handleSyncGroupBegin(Message msg) {\n        GroupSyncKey key = (GroupSyncKey)msg.body;\n        Log.i(TAG, \"sync group begin...:\" + key.groupID + \" \" + key.syncKey);\n    }\n\n    private void handleSyncGroupEnd(Message msg) {\n        GroupSyncKey key = (GroupSyncKey)msg.body;\n        Log.i(TAG, \"sync group end...:\" + key.groupID + \" \" + key.syncKey);\n\n        if (receivedGroupMessages.size() > 0) {\n            if (groupMessageHandler != null && !groupMessageHandler.handleMessages(receivedGroupMessages)) {\n                Log.i(TAG, \"handle group messages fail\");\n                return;\n            }\n            publishGroupMessages(receivedGroupMessages);\n            receivedGroupMessages.clear();\n        }\n\n        GroupSync s = null;\n        if (this.groupSyncKeys.containsKey(key.groupID)) {\n            s = this.groupSyncKeys.get(key.groupID);\n        } else {\n            Log.e(TAG, \"no group:\" + key.groupID + \" sync key\");\n            return;\n        }\n\n        if (key.syncKey != s.syncKey) {\n            s.syncKey = key.syncKey;\n            if (this.syncKeyHandler != null) {\n                this.syncKeyHandler.saveGroupSyncKey(key.groupID, key.syncKey);\n                this.sendGroupSyncKey(key.groupID, key.syncKey);\n            }\n        }\n\n        s.isSyncing = false;\n\n        int now = now();\n        if (s.pendingSyncKey > s.syncKey) {\n            //上次同步过程中，再次收到了新的SyncGroupNotify消息\n            this.sendGroupSync(s.groupID, s.syncKey);\n            s.isSyncing = true;\n            s.syncTimestamp = now;\n            s.pendingSyncKey = 0;\n        }\n    }\n\n    private void handleMetadata(Message msg) {\n        this.metaMessage = msg;\n    }\n\n    private void handlePong(Message msg) {\n        this.pingTimestamp = 0;\n    }\n\n    private void handleMessage(Message msg) {\n        Log.i(TAG, \"message cmd:\" + msg.cmd);\n\n        //处理服务器推到客户端的消息,\n        Metadata metadata = null;\n        if ((msg.flag & Flag.MESSAGE_FLAG_PUSH) != 0) {\n            Message metaMessage = this.metaMessage;\n            this.metaMessage = null;\n            if (metaMessage != null && metaMessage.seq + 1 == msg.seq) {\n                metadata = (Metadata)metaMessage.body;\n            } else {\n                return;\n            }\n\n            if (metadata.prevSyncKey == 0 || metadata.syncKey == 0) {\n                return;\n            }\n\n            if ((msg.flag & Flag.MESSAGE_FLAG_SUPER_GROUP) != 0) {\n                if (msg.cmd != Command.MSG_GROUP_IM) {\n                    return;\n                }\n\n                IMMessage m = (IMMessage)msg.body;\n                long groupID = m.receiver;\n                GroupSync s = null;\n                if (this.groupSyncKeys.containsKey(groupID)) {\n                    s = this.groupSyncKeys.get(groupID);\n                } else {\n                    return;\n                }\n\n                if (metadata.prevSyncKey != s.syncKey) {\n                    Log.i(TAG, \"super group sync key is not sequence:\" + metadata.prevSyncKey + \"-----\" + s.syncKey + \", ignore push message\");\n                    return;\n                }\n\n            } else {\n                if (metadata.prevSyncKey != this.syncKey) {\n                    Log.i(TAG, \"sync key is not sequence:\" + metadata.prevSyncKey + \"-----\" + this.syncKey + \", ignore push message\");\n                    return;\n                }\n            }\n        }\n\n        if (msg.cmd == Command.MSG_AUTH_STATUS) {\n            handleAuthStatus(msg);\n        } else if (msg.cmd == Command.MSG_IM) {\n            handleIMMessage(msg);\n        } else if (msg.cmd == Command.MSG_ACK) {\n            handleACK(msg);\n        } else if (msg.cmd == Command.MSG_PONG) {\n            handlePong(msg);\n        } else if (msg.cmd == Command.MSG_GROUP_IM) {\n            handleGroupIMMessage(msg);\n        } else if (msg.cmd == Command.MSG_GROUP_NOTIFICATION) {\n            handleGroupNotification(msg);\n        } else if (msg.cmd == Command.MSG_SYSTEM) {\n            handleSystemMessage(msg);\n        } else if (msg.cmd == Command.MSG_RT) {\n            handleRTMessage(msg);\n        } else if (msg.cmd == Command.MSG_CUSTOMER) {\n            handleCustomerMessage(msg);\n        } else if (msg.cmd == Command.MSG_ROOM_IM) {\n            handleRoomMessage(msg);\n        } else if (msg.cmd == Command.MSG_SYNC_NOTIFY) {\n            handleSyncNotify(msg);\n        } else if (msg.cmd == Command.MSG_SYNC_BEGIN) {\n            handleSyncBegin(msg);\n        } else if (msg.cmd == Command.MSG_SYNC_END) {\n            handleSyncEnd(msg);\n        } else if (msg.cmd == Command.MSG_SYNC_GROUP_NOTIFY) {\n            handleSyncGroupNotify(msg);\n        } else if (msg.cmd == Command.MSG_SYNC_GROUP_BEGIN) {\n            handleSyncGroupBegin(msg);\n        } else if (msg.cmd == Command.MSG_SYNC_GROUP_END) {\n            handleSyncGroupEnd(msg);\n        } else if (msg.cmd == Command.MSG_METADATA) {\n            handleMetadata(msg);\n        } else {\n            Log.i(TAG, \"unknown message cmd:\"+msg.cmd);\n        }\n\n        //保存synckey\n        if ((msg.flag & Flag.MESSAGE_FLAG_PUSH) != 0) {\n            long newSyncKey = metadata.syncKey;\n            if ((msg.flag & Flag.MESSAGE_FLAG_SUPER_GROUP) != 0) {\n                if (msg.cmd != Command.MSG_GROUP_IM) {\n                    return;\n                }\n\n                IMMessage m = (IMMessage)msg.body;\n                long groupID = m.receiver;\n                GroupSync s = null;\n                if (this.groupSyncKeys.containsKey(groupID)) {\n                    s = this.groupSyncKeys.get(groupID);\n                } else {\n                    return;\n                }\n\n                if (newSyncKey != s.syncKey) {\n                    s.syncKey = newSyncKey;\n                    if (this.syncKeyHandler != null) {\n                        this.syncKeyHandler.saveGroupSyncKey(groupID, s.syncKey);\n                        this.sendGroupSyncKey(groupID, s.syncKey);\n                    }\n                }\n            } else {\n                if (newSyncKey != this.syncKey) {\n                    this.syncKey = newSyncKey;\n                    if (this.syncKeyHandler != null) {\n                        this.syncKeyHandler.saveSyncKey(this.syncKey);\n                        this.sendSyncKey(this.syncKey);\n                    }\n                }\n            }\n        }\n    }\n\n    private void appendData(byte[] data) {\n        if (this.data != null) {\n            int l = this.data.length + data.length;\n            byte[] buf = new byte[l];\n            System.arraycopy(this.data, 0, buf, 0, this.data.length);\n            System.arraycopy(data, 0, buf, this.data.length, data.length);\n            this.data = buf;\n        } else {\n            this.data = data;\n        }\n    }\n\n    private boolean handleData(byte[] data) {\n        appendData(data);\n\n        int pos = 0;\n        while (true) {\n            if (this.data.length < pos + 4) {\n                break;\n            }\n            int len = BytePacket.readInt32(this.data, pos);\n            if (this.data.length < pos + 4 + Message.HEAD_SIZE + len) {\n                break;\n            }\n            Message msg = new Message();\n            byte[] buf = new byte[Message.HEAD_SIZE + len];\n            System.arraycopy(this.data, pos+4, buf, 0, Message.HEAD_SIZE+len);\n            if (!msg.unpack(buf)) {\n                Log.i(TAG, \"unpack message error\");\n                return false;\n            }\n            handleMessage(msg);\n            pos += 4 + Message.HEAD_SIZE + len;\n        }\n\n        byte[] left = new byte[this.data.length - pos];\n        System.arraycopy(this.data, pos, left, 0, left.length);\n        this.data = left;\n        return true;\n    }\n\n    private void sendAuth() {\n        final int PLATFORM_ANDROID = 2;\n\n        Message msg = new Message();\n        msg.cmd = Command.MSG_AUTH_TOKEN;\n        AuthenticationToken auth = new AuthenticationToken();\n        auth.platformID = PLATFORM_ANDROID;\n        auth.token = this.token;\n        auth.deviceID = this.deviceID;\n        msg.body = auth;\n\n        sendMessage(msg);\n    }\n\n    private void sendSync(long syncKey) {\n        Message msg = new Message();\n        msg.cmd = Command.MSG_SYNC;\n        msg.body = new Long(syncKey);\n        sendMessage(msg);\n    }\n\n    private void sendSyncKey(long syncKey) {\n        Message msg = new Message();\n        msg.cmd = Command.MSG_SYNC_KEY;\n        msg.body = new Long(syncKey);\n        sendMessage(msg);\n    }\n\n    private void sendGroupSync(long groupID, long syncKey) {\n        Message msg = new Message();\n        msg.cmd = Command.MSG_SYNC_GROUP;\n        GroupSyncKey key = new GroupSyncKey();\n        key.groupID = groupID;\n        key.syncKey = syncKey;\n        msg.body = key;\n        sendMessage(msg);\n    }\n\n    private void sendGroupSyncKey(long groupID, long syncKey) {\n        Message msg = new Message();\n        msg.cmd = Command.MSG_GROUP_SYNC_KEY;\n        GroupSyncKey key = new GroupSyncKey();\n        key.groupID = groupID;\n        key.syncKey = syncKey;\n        msg.body = key;\n        sendMessage(msg);\n    }\n\n    private void sendACK(int seq) {\n        MessageACK a = new MessageACK();\n        a.seq = seq;\n        Message ack = new Message();\n        ack.cmd = Command.MSG_ACK;\n        ack.body = a;\n        sendMessage(ack);\n    }\n\n    private void sendHeartbeat() {\n        if (connectState == ConnectState.STATE_CONNECTED && this.pingTimestamp == 0) {\n            Log.i(TAG, \"send ping\");\n            Message msg = new Message();\n            msg.cmd = Command.MSG_PING;\n            sendMessage(msg);\n\n            this.pingTimestamp = now();\n\n            Timer t = new Timer() {\n                @Override\n                protected void fire() {\n                    int now = now();\n                    //3s未收到pong\n                    if (pingTimestamp > 0 && now - pingTimestamp >= 3) {\n                        Log.i(TAG, \"ping timeout\");\n                        handleClose();\n                        return;\n                    }\n                }\n            };\n\n            t.setTimer(uptimeMillis()+1000*3+100);\n            t.resume();\n        }\n    }\n\n    private boolean sendMessage(Message msg) {\n        if (this.tcp == null || connectState != ConnectState.STATE_CONNECTED) return false;\n        this.seq++;\n        msg.seq = this.seq;\n        byte[] p = msg.pack();\n        if (p.length >= 32*1024) {\n            Log.e(TAG, \"message length overflow\");\n            return false;\n        }\n        int l = p.length - Message.HEAD_SIZE;\n        byte[] buf = new byte[p.length + 4];\n        BytePacket.writeInt32(l, buf, 0);\n        System.arraycopy(p, 0, buf, 4, p.length);\n        this.tcp.writeData(buf);\n        return true;\n    }\n\n    private void publishGroupMessages(final List<IMMessage> msgs) {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < groupObservers.size(); i++ ) {\n                    GroupMessageObserver ob = groupObservers.get(i);\n                    ob.onGroupMessages(msgs);\n                }\n            }\n        });\n    }\n\n    private void publishGroupMessageACK(final IMMessage im, final int error) {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < groupObservers.size(); i++) {\n                    GroupMessageObserver ob = groupObservers.get(i);\n                    ob.onGroupMessageACK(im, error);\n                }\n            }\n        });\n    }\n\n    private void publishGroupMessageFailure(final IMMessage im) {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < groupObservers.size(); i++) {\n                    GroupMessageObserver ob = groupObservers.get(i);\n                    ob.onGroupMessageFailure(im);\n                }\n            }\n        });\n    }\n\n    private void publishPeerMessage(final IMMessage msg) {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < peerObservers.size(); i++) {\n                    PeerMessageObserver ob = peerObservers.get(i);\n                    ob.onPeerMessage(msg);\n                }\n            }\n        });\n    }\n\n    private void publishPeerSecretMessage(final IMMessage msg) {\n\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < peerObservers.size(); i++) {\n                    PeerMessageObserver ob = peerObservers.get(i);\n                    ob.onPeerSecretMessage(msg);\n                }\n            }\n        });\n\n    }\n\n    private void publishPeerMessageACK(final IMMessage msg, final int error) {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < peerObservers.size(); i++ ) {\n                    PeerMessageObserver ob = peerObservers.get(i);\n                    ob.onPeerMessageACK(msg, error);\n                }\n            }\n        });\n\n    }\n\n    private void publishPeerMessageFailure(final IMMessage msg) {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < peerObservers.size(); i++ ) {\n                    PeerMessageObserver ob = peerObservers.get(i);\n                    ob.onPeerMessageFailure(msg);\n                }\n            }\n        });\n\n\n    }\n\n    private void publishConnectState() {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < observers.size(); i++ ) {\n                    IMServiceObserver ob = observers.get(i);\n                    ob.onConnectState(connectState);\n                }\n            }\n        });\n\n    }\n\n    private void publishCustomerMessage(final CustomerMessage cs) {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < customerServiceMessageObservers.size(); i++) {\n                    CustomerMessageObserver ob = customerServiceMessageObservers.get(i);\n                    ob.onCustomerMessage(cs);\n                }\n            }\n        });\n\n    }\n\n    private void publishCustomerServiceMessageACK(final CustomerMessage msg) {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < customerServiceMessageObservers.size(); i++) {\n                    CustomerMessageObserver ob = customerServiceMessageObservers.get(i);\n                    ob.onCustomerMessageACK(msg);\n                }\n            }\n        });\n\n    }\n\n\n    private void publishCustomerServiceMessageFailure(final CustomerMessage msg) {\n        runOnMainThread(new Runnable() {\n            @Override\n            public void run() {\n                for (int i = 0; i < customerServiceMessageObservers.size(); i++) {\n                    CustomerMessageObserver ob = customerServiceMessageObservers.get(i);\n                    ob.onCustomerMessageFailure(msg);\n                }\n            }\n        });\n    }\n\n    private void runOnMainThread(Runnable r) {\n        runOnThread(Looper.getMainLooper(), mainThreadHandler, r);\n    }\n\n    private void runOnWorkerThread(Runnable r) {\n        runOnThread(looper, handler, r);\n    }\n\n    private void runOnThread(Looper looper, Handler handler, Runnable r) {\n        if (Looper.myLooper() == looper) {\n            r.run();\n        } else {\n            handler.post(r);\n        }\n    }\n\n    private void assertLooper() {\n        if (Looper.myLooper() != looper) {\n            throw new AssertionError(\"looper assert\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/IMServiceObserver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 14-7-23.\n */\npublic interface IMServiceObserver {\n    public void onConnectState(IMService.ConnectState state);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/Message.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\nimport android.util.Log;\nimport java.util.Arrays;\n\n/**\n * Created by houxh on 14-7-23.\n */\n\nclass Command{\n    public static final int MSG_HEARTBEAT = 1;\n    public static final int MSG_AUTH = 2;\n    public static final int MSG_AUTH_STATUS = 3;\n    public static final int MSG_IM = 4;\n    public static final int MSG_ACK = 5;\n    public static final int MSG_RST = 6;\n    public static final int MSG_GROUP_NOTIFICATION = 7;\n    public static final int MSG_GROUP_IM = 8;\n    public static final int MSG_PEER_ACK = 9;\n    public static final int MSG_INPUTTING = 10;\n    public static final int MSG_SUBSCRIBE_ONLINE_STATE = 11;\n    public static final int MSG_ONLINE_STATE = 12;\n    public static final int MSG_PING = 13;\n    public static final int MSG_PONG = 14;\n    public static final int MSG_AUTH_TOKEN = 15;\n    public static final int MSG_LOGIN_POINT = 16;\n    public static final int MSG_RT = 17;\n    public static final int MSG_ENTER_ROOM = 18;\n    public static final int MSG_LEAVE_ROOM = 19;\n    public static final int MSG_ROOM_IM = 20;\n    public static final int MSG_SYSTEM = 21;\n    public static final int MSG_CUSTOMER_SERVICE = 23;\n\n    public static final int MSG_SYNC = 26;\n    public static final int MSG_SYNC_BEGIN = 27;\n    public static final int MSG_SYNC_END = 28;\n    public static final int MSG_SYNC_NOTIFY = 29;\n\n    public static final int MSG_SYNC_GROUP = 30;\n    public static final int MSG_SYNC_GROUP_BEGIN = 31;\n    public static final int MSG_SYNC_GROUP_END = 32;\n    public static final int MSG_SYNC_GROUP_NOTIFY = 33;\n\n    public static final int MSG_SYNC_KEY = 34;\n    public static final int MSG_GROUP_SYNC_KEY = 35;\n\n    public static final int MSG_METADATA = 37;\n\n    public static final int MSG_CUSTOMER = 64;\n}\n\nclass Flag {\n    public static final int MESSAGE_FLAG_TEXT = 1;\n    public static final int MESSAGE_FLAG_UNPERSISTENT = 2;\n    public static final int MESSAGE_FLAG_GROUP = 4;\n    public static final int MESSAGE_FLAG_SELF = 8;\n\n    //服务器主动下发的消息\n    public static final int MESSAGE_FLAG_PUSH = 0x10;\n\n    public static final int MESSAGE_FLAG_SUPER_GROUP = 0x20;\n}\n\n\nclass AuthenticationToken {\n    public String token;\n    public int platformID;\n    public String deviceID;\n}\n\n\n//个人消息：typedef long SyncKey\n\n//超级群\nclass GroupSyncKey {\n    public long groupID;\n    public long syncKey;\n}\n\nclass SyncNotify {\n    public long syncKey;\n}\n\nclass GroupSyncNotify {\n    public long groupID;\n    public long syncKey;\n}\n\nclass Metadata {\n    public long syncKey;\n    public long prevSyncKey;\n}\n\npublic class Message {\n\n    private static final int VERSION = 2;\n\n    public static final int HEAD_SIZE = 8;\n    public int cmd;\n    public int seq;\n    public int flag;\n    public Object body;\n\n    public int failCount;//发送失败的次数\n\n    public byte[] pack() {\n        int pos = 0;\n        byte[] buf = new byte[64*1024];\n        BytePacket.writeInt32(seq, buf, pos);\n        pos += 4;\n        buf[pos++] = (byte)cmd;\n        buf[pos++] = (byte)VERSION;\n        buf[pos++] = (byte)flag;\n        pos += 1;\n\n        if (cmd == Command.MSG_HEARTBEAT || cmd == Command.MSG_PING) {\n            return Arrays.copyOf(buf, HEAD_SIZE);\n        } else if (cmd == Command.MSG_AUTH) {\n            BytePacket.writeInt64((Long) body, buf, pos);\n            return Arrays.copyOf(buf, HEAD_SIZE + 8);\n        } else if (cmd == Command.MSG_AUTH_TOKEN) {\n            AuthenticationToken auth = (AuthenticationToken)body;\n            buf[pos] = (byte)auth.platformID;\n            pos++;\n            byte[] token = auth.token.getBytes();\n            buf[pos] = (byte)token.length;\n            pos++;\n            System.arraycopy(token, 0, buf, pos, token.length);\n            pos += token.length;\n\n            byte[] deviceID = auth.deviceID.getBytes();\n            buf[pos] = (byte)deviceID.length;\n            pos++;\n            System.arraycopy(deviceID, 0, buf, pos, deviceID.length);\n            pos += deviceID.length;\n\n            return Arrays.copyOf(buf, pos);\n        } else if (cmd == Command.MSG_IM || cmd == Command.MSG_GROUP_IM) {\n            IMMessage im = (IMMessage) body;\n            BytePacket.writeInt64(im.sender, buf, pos);\n            pos += 8;\n            BytePacket.writeInt64(im.receiver, buf, pos);\n            pos += 8;\n            BytePacket.writeInt32(im.timestamp, buf, pos);\n            pos += 4;\n            BytePacket.writeInt32(0, buf, pos);\n            pos += 4;\n            try {\n                byte[] c = im.content.getBytes(\"UTF-8\");\n                if (c.length + 24 >= 32 * 1024) {\n                    Log.e(\"imservice\", \"packet buffer overflow\");\n                    return null;\n                }\n                System.arraycopy(c, 0, buf, pos, c.length);\n                return Arrays.copyOf(buf, HEAD_SIZE + 24 + c.length);\n            } catch (Exception e) {\n                Log.e(\"imservice\", \"encode utf8 error\");\n                return null;\n            }\n        } else if (cmd == Command.MSG_ACK) {\n            MessageACK ack = (MessageACK)body;\n            BytePacket.writeInt32(ack.seq, buf, pos);\n            pos += 4;\n            buf[pos++] = (byte)ack.status;\n            return Arrays.copyOf(buf, HEAD_SIZE+5);\n        } else if (cmd == Command.MSG_CUSTOMER) {\n            CustomerMessage cs = (CustomerMessage) body;\n            BytePacket.writeInt64(cs.senderAppID, buf, pos);\n            pos += 8;\n            BytePacket.writeInt64(cs.sender, buf, pos);\n            pos += 8;\n            BytePacket.writeInt64(cs.receiverAppID, buf, pos);\n            pos += 8;\n            BytePacket.writeInt64(cs.receiver, buf, pos);\n            pos += 8;\n            BytePacket.writeInt32(cs.timestamp, buf, pos);\n            pos += 4;\n            try {\n                byte[] c = cs.content.getBytes(\"UTF-8\");\n                if (c.length + 36 >= 32 * 1024) {\n                    Log.e(\"imservice\", \"packet buffer overflow\");\n                    return null;\n                }\n                System.arraycopy(c, 0, buf, pos, c.length);\n                return Arrays.copyOf(buf, HEAD_SIZE + 36 + c.length);\n            } catch (Exception e) {\n                Log.e(\"imservice\", \"encode utf8 error\");\n                return null;\n            }\n        } else if (cmd == Command.MSG_RT) {\n            RTMessage rt = (RTMessage) body;\n            BytePacket.writeInt64(rt.sender, buf, pos);\n            pos += 8;\n            BytePacket.writeInt64(rt.receiver, buf, pos);\n            pos += 8;\n            try {\n                byte[] c = rt.content.getBytes(\"UTF-8\");\n                if (c.length + 24 >= 32 * 1024) {\n                    Log.e(\"imservice\", \"packet buffer overflow\");\n                    return null;\n                }\n                System.arraycopy(c, 0, buf, pos, c.length);\n                return Arrays.copyOf(buf, HEAD_SIZE + 16 + c.length);\n            } catch (Exception e) {\n                Log.e(\"imservice\", \"encode utf8 error\");\n                return null;\n            }\n        } else if (cmd == Command.MSG_ROOM_IM) {\n            RoomMessage rm = (RoomMessage)body;\n\n            BytePacket.writeInt64(rm.sender, buf, pos);\n            pos += 8;\n            BytePacket.writeInt64(rm.receiver, buf, pos);\n            pos += 8;\n            try {\n                byte[] c = rm.content.getBytes(\"UTF-8\");\n                if (c.length + 24 >= 32 * 1024) {\n                    Log.e(\"imservice\", \"packet buffer overflow\");\n                    return null;\n                }\n                System.arraycopy(c, 0, buf, pos, c.length);\n                return Arrays.copyOf(buf, HEAD_SIZE + 16 + c.length);\n            } catch (Exception e) {\n                Log.e(\"imservice\", \"encode utf8 error\");\n                return null;\n            }\n        } else if (cmd == Command.MSG_ENTER_ROOM || cmd == Command.MSG_LEAVE_ROOM) {\n            Long roomID = (Long) body;\n            BytePacket.writeInt64(roomID, buf, pos);\n            return Arrays.copyOf(buf, HEAD_SIZE + 8);\n        } else if (cmd == Command.MSG_SYNC ||\n                cmd == Command.MSG_SYNC_KEY) {\n            Long syncKey = (Long) body;\n            BytePacket.writeInt64(syncKey, buf, pos);\n            return Arrays.copyOf(buf, HEAD_SIZE + 8);\n        } else if (cmd == Command.MSG_SYNC_GROUP ||\n                cmd == Command.MSG_GROUP_SYNC_KEY) {\n            GroupSyncKey syncKey = (GroupSyncKey)body;\n            BytePacket.writeInt64(syncKey.groupID, buf, pos);\n            pos += 8;\n            BytePacket.writeInt64(syncKey.syncKey, buf, pos);\n            pos += 8;\n            return Arrays.copyOf(buf, HEAD_SIZE + 16);\n        } else {\n            return null;\n        }\n    }\n\n    public boolean unpack(byte[] data) {\n        int pos = 0;\n        this.seq = BytePacket.readInt32(data, pos);\n        pos += 4;\n        cmd = data[pos];\n        flag = data[pos + 2];\n        pos += 4;\n        if (cmd == Command.MSG_PONG) {\n            return true;\n        } else if (cmd == Command.MSG_AUTH_STATUS) {\n            int status = BytePacket.readInt32(data, pos);\n            this.body = new Integer(status);\n            return true;\n        } else if (cmd == Command.MSG_IM || cmd == Command.MSG_GROUP_IM) {\n            IMMessage im = new IMMessage();\n            im.sender = BytePacket.readInt64(data, pos);\n            pos += 8;\n            im.receiver = BytePacket.readInt64(data, pos);\n            pos += 8;\n            im.timestamp = BytePacket.readInt32(data, pos);\n            pos += 4;\n            im.msgLocalID = BytePacket.readInt32(data, pos);\n            pos += 4;\n            try {\n                im.content = new String(data, pos, data.length - 32, \"UTF-8\");\n                this.body = im;\n                return true;\n            } catch (Exception e) {\n                return false;\n            }\n        } else if (cmd == Command.MSG_ACK) {\n            MessageACK ack = new MessageACK();\n            ack.seq = BytePacket.readInt32(data, pos);\n            pos += 4;\n            ack.status = data[pos];\n            this.body = ack;\n            return true;\n        } else if (cmd == Command.MSG_GROUP_NOTIFICATION) {\n            try {\n                this.body = new String(data, pos, data.length - HEAD_SIZE, \"UTF-8\");\n                return true;\n            } catch (Exception e) {\n                return false;\n            }\n        } else if (cmd == Command.MSG_SYSTEM) {\n            try {\n                this.body = new String(data, pos, data.length - HEAD_SIZE, \"UTF-8\");\n                return true;\n            } catch (Exception e) {\n                return false;\n            }\n        } else if (cmd == Command.MSG_CUSTOMER) {\n            CustomerMessage cs = new CustomerMessage();\n            cs.senderAppID = BytePacket.readInt64(data, pos);\n            pos += 8;\n            cs.sender = BytePacket.readInt64(data, pos);\n            pos += 8;\n            cs.receiverAppID = BytePacket.readInt64(data, pos);\n            pos += 8;\n            cs.receiver = BytePacket.readInt64(data, pos);\n            pos += 8;\n            cs.timestamp = BytePacket.readInt32(data, pos);\n            pos += 4;\n            try {\n                cs.content = new String(data, pos, data.length - 36 - HEAD_SIZE, \"UTF-8\");\n                this.body = cs;\n                return true;\n            } catch (Exception e) {\n                return false;\n            }\n        } else if (cmd == Command.MSG_RT) {\n            RTMessage rt = new RTMessage();\n            rt.sender = BytePacket.readInt64(data, pos);\n            pos += 8;\n            rt.receiver = BytePacket.readInt64(data, pos);\n            pos += 8;\n            try {\n                rt.content = new String(data, pos, data.length - pos, \"UTF-8\");\n                this.body = rt;\n                return true;\n            } catch (Exception e) {\n                return false;\n            }\n        } else if (cmd == Command.MSG_ROOM_IM) {\n            RoomMessage rt = new RoomMessage();\n            rt.sender = BytePacket.readInt64(data, pos);\n            pos += 8;\n            rt.receiver = BytePacket.readInt64(data, pos);\n            pos += 8;\n            try {\n                rt.content = new String(data, pos, data.length - pos, \"UTF-8\");\n                this.body = rt;\n                return true;\n            } catch (Exception e) {\n                return false;\n            }\n        } else if (cmd == Command.MSG_ENTER_ROOM || cmd == Command.MSG_LEAVE_ROOM) {\n            long roomID = BytePacket.readInt64(data, pos);\n            this.body = new Long(roomID);\n            return true;\n        } else if (cmd == Command.MSG_SYNC_BEGIN ||\n                cmd == Command.MSG_SYNC_END) {\n            long key = BytePacket.readInt64(data, pos);\n            this.body = new Long(key);\n            return true;\n        } else if (cmd == Command.MSG_SYNC_GROUP_BEGIN ||\n                cmd == Command.MSG_SYNC_GROUP_END) {\n            GroupSyncKey key = new GroupSyncKey();\n            key.groupID = BytePacket.readInt64(data, pos);\n            pos += 8;\n            key.syncKey = BytePacket.readInt64(data, pos);\n            pos += 8;\n            this.body = key;\n            return true;\n        } else if (cmd == Command.MSG_SYNC_NOTIFY) {\n            SyncNotify notify = new SyncNotify();\n            notify.syncKey = BytePacket.readInt64(data, pos);\n            pos += 8;\n            this.body = notify;\n            return true;\n        } else if (cmd == Command.MSG_SYNC_GROUP_NOTIFY) {\n            GroupSyncNotify key = new GroupSyncNotify();\n            key.groupID = BytePacket.readInt64(data, pos);\n            pos += 8;\n            key.syncKey = BytePacket.readInt64(data, pos);\n            pos += 8;\n            this.body = key;\n            return true;\n        } else if (cmd == Command.MSG_METADATA) {\n            Metadata key = new Metadata();\n            key.syncKey = BytePacket.readInt64(data, pos);\n            pos += 8;\n            key.prevSyncKey = BytePacket.readInt64(data, pos);\n            this.body = key;\n            return true;\n        } else {\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/MessageACK.java",
    "content": "package com.beetle.im;\n\n\npublic class MessageACK {\n    public int seq;\n    public int status;\n\n    public static final int MESSAGE_ACK_SUCCESS  = 0;\n    public static final int MESSAGE_ACK_NOT_MY_FRIEND = 1;\n    public static final int MESSAGE_ACK_NOT_YOUR_FRIEND = 2;\n    public static final int MESSAGE_ACK_IN_YOUR_BLACKLIST = 3;\n    public static final int MESSAGE_ACK_NOT_GROUP_MEMBER = 64;\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/PeerMessageHandler.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 14-7-23.\n */\npublic interface PeerMessageHandler {\n    public boolean handleMessage(IMMessage msg);\n    public boolean handleMessageACK(IMMessage msg, int error);\n    public boolean handleMessageFailure(IMMessage msg);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/PeerMessageObserver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 14-7-23.\n */\npublic interface PeerMessageObserver {\n    public void onPeerMessage(IMMessage msg);\n    public void onPeerSecretMessage(IMMessage msg);\n    public void onPeerMessageACK(IMMessage msg, int error);\n    public void onPeerMessageFailure(IMMessage msg);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/RTMessage.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 16/1/25.\n */\npublic class RTMessage {\n    public long sender;\n    public long receiver;\n    public String content;\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/RTMessageObserver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 16/1/25.\n */\npublic interface RTMessageObserver {\n    void onRTMessage(RTMessage rt);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/RoomMessage.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 16/5/14.\n */\npublic class RoomMessage {\n    public long sender;\n    public long receiver;\n    public String content;\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/RoomMessageObserver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 16/5/14.\n */\npublic interface RoomMessageObserver {\n    public void onRoomMessage(RoomMessage msg);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/SyncKeyHandler.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 2016/11/2.\n */\n\npublic interface SyncKeyHandler {\n    boolean saveSyncKey(long syncKey);\n    boolean saveGroupSyncKey(long groupID, long syncKey);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/SystemMessageObserver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\n/**\n * Created by houxh on 16/1/16.\n */\npublic interface SystemMessageObserver {\n    public void onSystemMessage(String sm);\n}\n"
  },
  {
    "path": "imsdk/src/main/java/com/beetle/im/Timer.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage com.beetle.im;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.util.Log;\nimport static android.os.SystemClock.uptimeMillis;\n\n/**\n * Created by houxh on 14-7-21.\n */\npublic abstract class Timer {\n    private static final int WHAT = 0;\n\n    private long start;\n    private long interval;\n    private boolean active = false;\n\n    class TimerHandler extends Handler {\n        @Override\n        public void handleMessage(Message msg) {\n            if (!active) {\n                return;\n            }\n\n            Timer.this.fire();\n            if (Timer.this.interval != -1) {\n                long t = uptimeMillis() + Timer.this.interval;\n                boolean b = this.sendEmptyMessageAtTime(WHAT, t);\n            }\n        }\n\n        public TimerHandler(Looper loop) {\n            super(loop);\n        }\n    }\n    private Handler handler;\n\n    public Timer(Looper looper) {\n        handler = new TimerHandler(looper);\n    }\n\n    public Timer() {\n        handler = new TimerHandler(Looper.myLooper());\n    }\n\n    public void setTimer(long start, long interval) {\n        this.start = start;\n        this.interval = interval;\n        if (active) {\n            handler.removeMessages(WHAT);\n            handler.sendEmptyMessageAtTime(WHAT, start);\n        }\n    }\n\n    public void setTimer(long start) {\n        this.start = start;\n        this.interval = -1;\n        if (active) {\n            handler.removeMessages(WHAT);\n            handler.sendEmptyMessageAtTime(WHAT, start);\n        }\n    }\n\n    public void resume() {\n        active = true;\n        handler.sendEmptyMessageAtTime(WHAT, start);\n    }\n\n    public void suspend() {\n        active = false;\n        handler.removeMessages(WHAT);\n    }\n\n    protected abstract void fire();\n}\n"
  },
  {
    "path": "imsdk/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">IMSDK</string>\n</resources>\n"
  },
  {
    "path": "push_demo/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "push_demo/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    package=\"io.gobelieve.im.demo\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\n    <!-- 【必须】 信鸽SDK所需权限 -->\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />\n    <uses-permission android:name=\"android.permission.RESTART_PACKAGES\" />\n    <uses-permission android:name=\"android.permission.BROADCAST_STICKY\" />\n    <uses-permission android:name=\"android.permission.WRITE_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    <uses-permission android:name=\"android.permission.KILL_BACKGROUND_PROCESSES\" />\n    <uses-permission android:name=\"android.permission.GET_TASKS\" />\n    <uses-permission android:name=\"android.permission.READ_LOGS\" />\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <!-- 【可选】 信鸽SDK所需权限 -->\n    <uses-permission android:name=\"android.permission.BLUETOOTH\" />\n    <uses-permission android:name=\"android.permission.BATTERY_STATS\" />\n\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>\n    <uses-permission android:name=\"android.permission.BATTERY_STATS\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.BROADCAST_STICKY\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />\n    <uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>\n\n    <!--高德地图所需权限-->\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n    <!--<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />-->\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n\n    <!-- 小米push所需的权限 begin-->\n    <!--<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />-->\n    <!--<uses-permission android:name=\"android.permission.INTERNET\" />-->\n    <!--<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />-->\n    <!--<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />-->\n    <!--<uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />-->\n    <uses-permission android:name=\"android.permission.GET_TASKS\" />\n    <!--<uses-permission android:name=\"android.permission.VIBRATE\" />-->\n\n    <!-- the following 2 io.gobelieve.im.demo should be changed to your package name -->\n    <permission\n        android:name=\"io.gobelieve.im.demo.permission.MIPUSH_RECEIVE\"\n        android:protectionLevel=\"signature\" />\n\n    <uses-permission android:name=\"io.gobelieve.im.demo.permission.MIPUSH_RECEIVE\" />\n    <!-- 小米push所需的权限 end-->\n\n    <!-- 华为push所需的权限 begin-->\n    <!--<uses-permission android:name=\"android.permission.INTERNET\" />-->\n    <!--<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />-->\n    <!--<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />-->\n    <!--<uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />-->\n    <!--<uses-permission android:name=\"android.permission.WAKE_LOCK\" />-->\n    <!-- 保存富媒体消息需要,无富媒体消息则不需要 -->\n    <!--<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />-->\n    <!-- 富媒体彩信消息需要，不推送彩信则不需要 -->\n    <!--<uses-permission android:name=\"android.permission.READ_SMS\" />-->\n    <!--<uses-permission android:name=\"android.permission.WRITE_SMS\" />-->\n    <!-- 创建桌面快捷方式,无富媒体消息则不需要 ， -->\n    <!--<uses-permission android:name=\"com.android.launcher.permission.INSTALL_SHORTCUT\" />-->\n    <!-- 根据地理位置推送消息需要事先上报地理位置信息，需要如下权限，不上报则不需要， -->\n    <!--<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>-->\n    <!--<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />-->\n    <!-- 华为push所需的权限 end-->\n\n    <application\n        android:name=\".PushDemoApplication\"\n        android:allowBackup=\"true\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\">\n\n\n        <activity\n            android:name=\".LoginActivity\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@style/Horizontal_Slide\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <!-- 必需： 应用ID -->\n        <meta-data\n            android:name=\"GOBELIEVE_APPID\"\n            android:value=\"7\" />\n\n        <!-- 必需： 应用KEY -->\n        <meta-data\n            android:name=\"GOBELIEVE_APPKEY\"\n            android:value=\"sVDIlIiDUm7tWPYWhi6kfNbrqui3ez44\" />\n\n\n        <activity\n            android:name=\"com.beetle.bauhinia.PeerMessageActivity\"\n            android:exported=\"true\"\n            android:windowSoftInputMode=\"adjustResize\"\n            android:theme=\"@style/imkit.NoActionBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\"com.beetle.bauhinia.activity.PhotoActivity\"\n            android:label=\"照片\"\n            android:theme=\"@style/imkit.ActionBar\">\n        </activity>\n\n\n        <!-- 【必须】 信鸽receiver广播接收 -->\n        <receiver\n            android:name=\"com.tencent.android.tpush.XGPushReceiver\"\n            android:process=\":xg_service_v2\" >\n            <intent-filter android:priority=\"0x7fffffff\" >\n                <!-- 【必须】 信鸽SDK的内部广播 -->\n                <action android:name=\"com.tencent.android.tpush.action.SDK\" />\n                <action android:name=\"com.tencent.android.tpush.action.INTERNAL_PUSH_MESSAGE\" />\n                <!-- 【必须】 系统广播：开屏和网络切换 -->\n                <action android:name=\"android.intent.action.USER_PRESENT\" />\n                <action android:name=\"android.net.conn.CONNECTIVITY_CHANGE\" />\n\n                <!-- 【可选】 一些常用的系统广播，增强信鸽service的复活机会，请根据需要选择。当然，你也可以添加APP自定义的一些广播让启动service -->\n                <action android:name=\"android.bluetooth.adapter.action.STATE_CHANGED\" />\n                <action android:name=\"android.intent.action.ACTION_POWER_CONNECTED\" />\n                <action android:name=\"android.intent.action.ACTION_POWER_DISCONNECTED\" />\n            </intent-filter>\n        </receiver>\n\n        <!-- 【必须】 (2.30及以上版新增)展示通知的activity -->\n        <!-- 【注意】 如果被打开的activity是启动模式为SingleTop，SingleTask或SingleInstance，请根据通知的异常自查列表第8点处理-->\n        <activity\n            android:name=\"com.tencent.android.tpush.XGPushActivity\"\n            android:exported=\"true\" >\n            <intent-filter>\n                <!-- 若使用AndroidStudio，请设置android:name=\"android.intent.action\"-->\n                <action android:name=\"android.intent.action\" />\n            </intent-filter>\n        </activity>\n\n\n        <!-- 【必须】 信鸽service -->\n        <service\n            android:name=\"com.tencent.android.tpush.service.XGPushService\"\n            android:exported=\"true\"\n            android:persistent=\"true\"\n            android:process=\":xg_service_v2\" />\n\n        <!-- 【必须】 通知service，此选项有助于提高抵达率 -->\n        <service\n            android:name=\"com.tencent.android.tpush.rpc.XGRemoteService\"\n            android:exported=\"true\" >\n            <intent-filter>\n                <action android:name=\"io.gobelieve.im.demo.PUSH_ACTION\" />\n            </intent-filter>\n        </service>\n\n        <receiver android:name=\"io.gobelieve.im.demo.XGMessageReceiver\"\n            android:exported=\"true\" >\n            <intent-filter>\n                <!-- 接收消息透传 -->\n                <action android:name=\"com.tencent.android.tpush.action.PUSH_MESSAGE\" />\n                <!-- 监听注册、反注册、设置/删除标签、通知被点击等处理结果 -->\n                <action android:name=\"com.tencent.android.tpush.action.FEEDBACK\" />\n            </intent-filter>\n        </receiver>\n\n\n\n        <!-- 【必须】 请将YOUR_ACCESS_ID修改为APP的AccessId，“21”开头的10位数字，中间没空格 -->\n        <meta-data\n            android:name=\"XG_V2_ACCESS_ID\"\n            android:value=\"2100103204\" />\n        <!-- 【必须】 请将YOUR_ACCESS_KEY修改为APP的AccessKey，“A”开头的12位字符串，中间没空格 -->\n        <meta-data\n            android:name=\"XG_V2_ACCESS_KEY\"\n            android:value=\"A96BY2QEU51D\" />\n\n\n        <!-- 华为推送相关 begin -->\n        <!-- 第三方相关 :接收Push消息（注册、Push消息、Push连接状态、标签，LBS上报结果）广播 -->\n        <receiver android:name=\"io.gobelieve.im.demo.HuaweiPushReceiver\" >\n            <intent-filter>\n                <!-- 必须,用于接收token-->\n                <action android:name=\"com.huawei.android.push.intent.REGISTRATION\" />\n                <!-- 必须，用于接收消息-->\n                <action android:name=\"com.huawei.android.push.intent.RECEIVE\" />\n                <!-- 可选，用于点击通知栏或通知栏上的按钮后触发onEvent回调-->\n                <action android:name=\"com.huawei.android.push.intent.CLICK\" />\n                <!-- 可选，查看push通道是否连接，不查看则不需要-->\n                <action android:name=\"com.huawei.intent.action.PUSH_STATE\" />\n                <!-- 可选，标签、地理位置上报回应，不上报则不需要 -->\n                <action android:name=\"com.huawei.android.push.plugin.RESPONSE\" />\n            </intent-filter>\n        </receiver>\n\n        <!-- 备注：Push相关的android组件需要添加到业务的AndroidManifest.xml,\n        \t Push相关android组件运行在另外一个进程是为了防止Push服务异常而影响主业务 -->\n\n        <!-- PushSDK:PushSDK接收外部请求事件入口 -->\n        <receiver\n            android:name=\"com.huawei.android.pushagent.PushEventReceiver\"\n            android:process=\":pushservice\" >\n            <intent-filter>\n                <action android:name=\"com.huawei.android.push.intent.REFRESH_PUSH_CHANNEL\" />\n                <action android:name=\"com.huawei.intent.action.PUSH\" />\n                <action android:name=\"com.huawei.intent.action.PUSH_ON\" />\n                <action android:name=\"com.huawei.android.push.PLUGIN\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.PACKAGE_ADDED\" />\n                <action android:name=\"android.intent.action.PACKAGE_REMOVED\" />\n\n                <data android:scheme=\"package\" />\n            </intent-filter>\n        </receiver>\n\n        <receiver\n            android:name=\"com.huawei.android.pushagent.PushBootReceiver\"\n            android:process=\":pushservice\" >\n            <intent-filter>\n                <action android:name=\"com.huawei.android.push.intent.REGISTER\" />\n                <action android:name=\"android.net.conn.CONNECTIVITY_CHANGE\" />\n            </intent-filter>\n            <meta-data\n                android:name=\"CS_cloud_version\"\n                android:value=\"\\u0032\\u0035\\u0035\\u0039\" />\n        </receiver>\n\n        <!-- PushSDK:Push服务 -->\n        <service\n            android:name=\"com.huawei.android.pushagent.PushService\"\n            android:process=\":pushservice\" >\n        </service>\n\n        <!-- PushSDK:富媒体呈现页面，用于呈现服务器下发的富媒体消息 -->\n        <activity\n            android:name=\"com.huawei.android.pushselfshow.richpush.RichPushActivity\"\n            android:process=\":pushservice\"\n            android:theme=\"@style/hwpush_NoActionBar\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"orientation|screenSize|locale|layoutDirection\">\n            <meta-data android:name=\"hwc-theme\"\n                android:value=\"androidhwext:style/Theme.Emui\"/>\n            <intent-filter>\n                <action android:name=\"com.huawei.android.push.intent.RICHPUSH\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </activity>\n\n        <!-- PushSDK:用于标识Appid信息 -->\n        <meta-data\n            android:name=\"APPKEY\"\n            android:value=\"10417737\" >\n        </meta-data>\n\n        <!-- PushSDK:用于标识渠道信息 -->\n        <meta-data\n            android:name=\"CHANNEL\"\n            android:value=\"push\" >\n        </meta-data>\n        <!-- 华为推送相关 end -->\n\n\n        <!-- 小米推送相关 begin -->\n        <service\n            android:name=\"com.xiaomi.push.service.XMPushService\"\n            android:enabled=\"true\"\n            android:process=\":pushservice\" />\n        <service\n            android:name=\"com.xiaomi.mipush.sdk.PushMessageHandler\"\n            android:enabled=\"true\"\n            android:exported=\"true\" />\n        <service android:enabled=\"true\"\n            android:name=\"com.xiaomi.mipush.sdk.MessageHandleService\" />\n\n        <receiver\n            android:name=\"io.gobelieve.im.demo.XiaomiPushReceiver\"\n            android:exported=\"true\" >\n            <intent-filter>\n                <action android:name=\"com.xiaomi.mipush.RECEIVE_MESSAGE\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"com.xiaomi.mipush.MESSAGE_ARRIVED\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"com.xiaomi.mipush.ERROR\" />\n            </intent-filter>\n        </receiver>\n        <receiver\n            android:name=\"com.xiaomi.push.service.receivers.NetworkStatusReceiver\"\n            android:exported=\"true\" >\n            <intent-filter>\n                <action android:name=\"android.net.conn.CONNECTIVITY_CHANGE\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </receiver>\n        <receiver\n            android:name=\"com.xiaomi.push.service.receivers.PingReceiver\"\n            android:exported=\"false\"\n            android:process=\":pushservice\" >\n            <intent-filter>\n                <action android:name=\"com.xiaomi.push.PING_TIMER\" />\n            </intent-filter>\n        </receiver>\n        <!-- 小米推送相关 end -->\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "push_demo/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 24\n    buildToolsVersion '27.0.3'\n    useLibrary  'org.apache.http.legacy'\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion 24\n        versionCode 1020\n        versionName \"1.0.2\"\n    }\n\n\n    sourceSets {\n        main {\n            manifest.srcFile 'AndroidManifest.xml'\n            java.srcDirs = ['src']\n            res.srcDirs = ['res']\n            aidl.srcDirs = ['src']\n            jniLibs.srcDirs = ['libs']\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n\n    implementation 'com.android.support:appcompat-v7:24.2.0'\n\n    compile 'com.google.code.gson:gson:2.+'\n    compile 'com.netflix.rxjava:rxjava-core:0.17.+'\n    compile 'com.netflix.rxjava:rxjava-android:0.17.+'\n    compile 'com.squareup.okhttp:okhttp:2.1.+'\n    compile 'com.squareup.okhttp:okhttp-urlconnection:2.1.+'\n    compile 'com.squareup.retrofit:retrofit:1.7.+'\n    compile 'com.squareup.picasso:picasso:2.4.+'\n    compile 'com.squareup:otto:1.3.5'\n    compile 'com.jakewharton:butterknife:6.0.+'\n    compile 'org.apache.commons:commons-io:1.3.2'\n\n    compile 'joda-time:joda-time:2.5'\n\n    compile project(':imkit')\n    compile project(':imsdk')\n    compile project(':asynctcp')\n}\n\nandroid {\n    packagingOptions {\n        exclude 'META-INF/DEPENDENCIES.txt'\n        exclude 'META-INF/LICENSE.txt'\n        exclude 'META-INF/NOTICE.txt'\n        exclude 'META-INF/NOTICE'\n        exclude 'META-INF/LICENSE'\n        exclude 'META-INF/DEPENDENCIES'\n        exclude 'META-INF/notice.txt'\n        exclude 'META-INF/license.txt'\n        exclude 'META-INF/dependencies.txt'\n        exclude 'META-INF/LGPL2.1'\n    }\n}\n\n\ndef appendVersionNameVersionCode(variant, defaultConfig) {\n    //check if staging variant\n    if (variant.name == android.buildTypes.release.name) {\n        if (variant.buildType.zipAlignEnabled) {\n            variant.outputs.each{output ->\n                def outputFile = output.outputFile\n                if (outputFile != null && outputFile.name.endsWith('.apk')){\n                    def fileName = outputFile.name.replace(\".apk\", \"-v\" + defaultConfig.versionName + \".apk\")\n                    output.outputFile = new File(outputFile.parent, fileName)\n                }\n\n            }\n        }\n       /* def file = variant.buildType.packageApplication.outputFile\n        def fileName = file.name.replace(\".apk\", \"-v\" + defaultConfig.versionName + \".apk\")\n        variant.packageApplication.outputFile = new File(file.parent, fileName)*/\n    }\n}\n"
  },
  {
    "path": "push_demo/proguard-rules.txt",
    "content": "#\n# This ProGuard configuration file illustrates how to process Android\n# applications.\n# Usage:\n#     java -jar proguard.jar @android.pro\n#\n# If you're using the Android SDK (version 2.3 or higher), the android tool\n# already creates a file like this in your project, called proguard.cfg.\n# It should contain the settings of this file, minus the input and output paths\n# (-injars, -outjars, -libraryjars, -printmapping, and -printseeds).\n# The generated Ant build file automatically sets these paths.\n\n# Specify the input jars, output jars, and library jars.\n# Note that ProGuard works with Java bytecode (.class),\n# before the dex compiler converts it into Dalvik code (.dex).\n\n-injars build/intermediates/classes/debug\n-outjars bin/classes-processed.jar\n\n-libraryjars  libs\n#-libraryjars /Users/walker/develop/android-sdk-macosx/platforms/android-19/android.jar\n#-libraryjars /usr/local/android-sdk/add-ons/google_apis-7_r01/libs/maps.jar\n# ...\n\n# Save the obfuscation mapping to a file, so you can de-obfuscate any stack\n# traces later on.\n\n\n# You can print out the seeds that are matching the keep options below.\n\n#-printseeds bin/classes-processed.seeds\n\n# Preverification is irrelevant for the dex compiler and the Dalvik VM.\n\n-dontpreverify\n\n# Reduce the size of the output some more.\n\n#-repackageclasses ''\n-allowaccessmodification\n\n# Switch off some optimizations that trip older versions of the Dalvik VM.\n\n-optimizations !code/simplification/arithmetic\n\n# Keep a fixed source file attribute and all line number tables to get line\n# numbers in the stack traces.\n# You can comment this out if you're not interested in stack traces.\n\n-renamesourcefileattribute SourceFile\n-keepattributes SourceFile,LineNumberTable\n\n# RemoteViews might need annotations.\n\n-keepattributes *Annotation*\n\n# Preserve all fundamental application classes.\n\n-keep public class * extends android.app.Activity\n-keep public class * extends android.app.Application\n-keep public class * extends android.app.Service\n-keep public class * extends android.content.BroadcastReceiver\n-keep public class * extends android.content.ContentProvider\n\n# Preserve all View implementations, their special context constructors, and\n# their setters.\n\n-keep public class * extends android.view.View {\n    public <init>(android.content.Context);\n    public <init>(android.content.Context, android.util.AttributeSet);\n    public <init>(android.content.Context, android.util.AttributeSet, int);\n    public void set*(...);\n}\n\n# Preserve all classes that have special context constructors, and the\n# constructors themselves.\n\n-keepclasseswithmembers class * {\n    public <init>(android.content.Context, android.util.AttributeSet);\n}\n\n# Preserve all classes that have special context constructors, and the\n# constructors themselves.\n\n-keepclasseswithmembers class * {\n    public <init>(android.content.Context, android.util.AttributeSet, int);\n}\n\n# Preserve the special fields of all Parcelable implementations.\n\n-keepclassmembers class * implements android.os.Parcelable {\n    static android.os.Parcelable$Creator CREATOR;\n}\n\n\n\n# Preserve static fields of inner classes of R classes that might be accessed\n# through introspection.\n\n-keepclassmembers class **.R$* {\n  public static <fields>;\n}\n\n# Preserve the required interface from the License Verification Library\n# (but don't nag the developer if the library is not used at all).\n\n-keep public interface com.android.vending.licensing.ILicensingService\n\n-dontnote com.android.vending.licensing.ILicensingService\n\n# The Android Compatibility library references some classes that may not be\n# present in all versions of the API, but we know that's ok.\n\n-dontwarn android.support.**\n\n-dontwarn com.gameserivce.sdk.push.**\n-dontwarn com.gameserivce.sdk.analystic.**\n-dontwarn com.gameserivce.sdk.im.**\n-keep class com.gameserivce.sdk.push.** { *; }\n-keep class com.gameserivce.sdk.analystic.** { *; }\n-keep class com.gameserivce.sdk.im.** { *; }\n\n# Preserve all native method names and the names of their classes.\n\n-keepclasseswithmembernames class * {\n    native <methods>;\n}\n\n# Preserve the special static methods that are required in all enumeration\n# classes.\n\n-keepclassmembers class * extends java.lang.Enum {\n    public static **[] values();\n    public static ** valueOf(java.lang.String);\n}\n\n# Explicitly preserve all serialization members. The Serializable interface\n# is only a marker interface, so it wouldn't save them.\n# You can comment this out if your application doesn't use serialization.\n# If your code contains serializable classes that have to be backward \n# compatible, please refer to the manual.\n\n-keepclassmembers class * implements java.io.Serializable {\n    static final long serialVersionUID;\n    static final java.io.ObjectStreamField[] serialPersistentFields;\n    private void writeObject(java.io.ObjectOutputStream);\n    private void readObject(java.io.ObjectInputStream);\n    java.lang.Object writeReplace();\n    java.lang.Object readResolve();\n}\n\n# Your application may contain more items that need to be preserved; \n# typically classes that are dynamically created using Class.forName:\n\n# -keep public class mypackage.MyClass\n# -keep public interface mypackage.MyInterface\n# -keep public class * implements mypackage.MyInterface\n"
  },
  {
    "path": "push_demo/res/anim/fade_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n    android:duration=\"300\" />"
  },
  {
    "path": "push_demo/res/anim/fade_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:fromAlpha=\"1.0\" android:toAlpha=\"0.0\"\n    android:duration=\"300\" />"
  },
  {
    "path": "push_demo/res/anim/head_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 左上角扩大-->\n  <scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"0.001\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"0.001\"   \n        android:toYScale=\"1.0\"   \n        android:pivotX=\"15%\"  \n        android:pivotY=\"25%\"  \n        android:duration=\"200\" />  \n   "
  },
  {
    "path": "push_demo/res/anim/head_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 左上角缩小 -->\n  <scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"0.001\"   \n        android:fromYScale=\"1.0\"   \n        android:toYScale=\"0.001\"   \n        android:pivotX=\"15%\"  \n        android:pivotY=\"25%\"  \n        android:duration=\"200\" />  \n   "
  },
  {
    "path": "push_demo/res/anim/hold.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?> \n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:interpolator=\"@android:anim/accelerate_interpolator\"\n       android:fromXDelta=\"0\" android:toXDelta=\"0\"\n       android:duration=\"300\" />"
  },
  {
    "path": "push_demo/res/anim/push_bottom_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"50%p\"\n        android:toYDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "push_demo/res/anim/push_bottom_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    \n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"50%p\" />\n\n    \n\n</set>"
  },
  {
    "path": "push_demo/res/anim/push_top_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"-50%p\"\n        android:toYDelta=\"0\" />   \n</set>"
  },
  {
    "path": "push_demo/res/anim/push_top_in2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"0\"   \n        android:toYScale=\"1.0\"   \n        android:pivotX=\"0\"  \n        android:pivotY=\"10%\"  \n        android:duration=\"200\" /> "
  },
  {
    "path": "push_demo/res/anim/push_top_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"-50%p\" />   \n</set>"
  },
  {
    "path": "push_demo/res/anim/push_top_out2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"1.0\"   \n        android:toYScale=\"0\"   \n        android:pivotX=\"0\"  \n        android:pivotY=\"10%\"  \n        android:duration=\"200\" /> "
  },
  {
    "path": "push_demo/res/anim/slide_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"-100%p\"\n        android:toXDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "push_demo/res/anim/slide_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"100%p\"\n        android:toXDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "push_demo/res/anim/slide_out_to_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"0\"\n        android:toXDelta=\"-100%p\" />\n\n</set>"
  },
  {
    "path": "push_demo/res/anim/slide_out_to_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"0\"\n        android:toXDelta=\"100%p\" />\n\n</set>"
  },
  {
    "path": "push_demo/res/drawable/btn_login_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/btn_login_pressed\" android:state_pressed=\"true\" />\n    <item android:drawable=\"@drawable/btn_login_normal\" />\n\n\n</selector>\n"
  },
  {
    "path": "push_demo/res/drawable/hwpush_btn_checkbox_list_star.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<!-- Copyright (C) 2008 The Android Open Source Project\r\n\r\n     Licensed under the Apache License, Version 2.0 (the \"License\");\r\n     you may not use this file except in compliance with the License.\r\n     You may obtain a copy of the License at\r\n\r\n          http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n     Unless required by applicable law or agreed to in writing, software\r\n     distributed under the License is distributed on an \"AS IS\" BASIS,\r\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n     See the License for the specific language governing permissions and\r\n     limitations under the License.\r\n-->\r\n\r\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\r\n    <item android:state_checked=\"true\" \r\n          android:state_pressed=\"true\"\r\n          android:drawable=\"@drawable/hwpush_btn_check_on_pressed_emui\" />\r\n    <item android:state_checked=\"true\" \r\n          android:state_pressed=\"false\"\r\n          android:drawable=\"@drawable/hwpush_btn_check_on_emui\" />\r\n    <item android:state_checked=\"false\" \r\n          android:state_pressed=\"true\"\r\n          android:drawable=\"@drawable/hwpush_btn_check_off_pressed_emui\" />\r\n    <item android:state_checked=\"false\" \r\n          android:state_pressed=\"false\"\r\n          android:drawable=\"@drawable/hwpush_btn_check_off_emui\" />\r\n</selector>\r\n"
  },
  {
    "path": "push_demo/res/layout/activity_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_background\">\n\n    <!--发送用户id-->\n    <View\n        android:id=\"@+id/line_sender_underline\"\n        style=\"@style/login_edittext_underline\"\n        android:layout_marginTop=\"248dp\" />\n\n    <ImageView\n        android:id=\"@+id/iv_sender\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_above=\"@id/line_sender_underline\"\n        android:layout_alignBottom=\"@id/line_sender_underline\"\n        android:layout_marginBottom=\"9dp\"\n        android:layout_marginLeft=\"22dp\"\n        android:src=\"@drawable/ic_account\" />\n\n    <EditText\n        android:id=\"@+id/et_username\"\n        style=\"@style/login_edittext\"\n        android:layout_above=\"@id/line_sender_underline\"\n        android:layout_alignBottom=\"@id/line_sender_underline\"\n        android:layout_toRightOf=\"@id/iv_sender\"\n        android:hint=\"@string/login_account\" />\n\n    <!--接收用户id-->\n\n    <View\n        android:id=\"@+id/line_receiver_underline\"\n        style=\"@style/login_edittext_underline\"\n        android:layout_below=\"@id/line_sender_underline\"\n        android:layout_marginTop=\"44dp\" />\n\n\n    <ImageView\n        android:id=\"@+id/iv_receiver\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignBottom=\"@id/line_receiver_underline\"\n        android:layout_marginBottom=\"9dp\"\n        android:layout_marginLeft=\"22dp\"\n        android:src=\"@drawable/ic_account\" />\n\n    <EditText\n        android:id=\"@+id/et_target_username\"\n        style=\"@style/login_edittext\"\n        android:layout_alignBottom=\"@id/line_receiver_underline\"\n        android:layout_toRightOf=\"@id/iv_receiver\"\n        android:hint=\"@string/login_target_account\" />\n\n    <Button\n        android:id=\"@+id/btn_login\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/line_receiver_underline\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginRight=\"15dp\"\n        android:layout_marginTop=\"45dp\"\n        android:background=\"@drawable/btn_login_selector\"\n        android:text=\"@string/login_login\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"@dimen/h2\" />\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "push_demo/res/layout/hwpush_buttons_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"match_parent\"\r\n    android:layout_marginRight=\"16dp\"\r\n    android:orientation=\"vertical\" >\r\n    <ImageView \r\n        android:id=\"@+id/small_btn\"\r\n        android:layout_width=\"32dip\"\r\n        android:layout_height=\"32dip\"\r\n        android:adjustViewBounds=\"true\"\r\n        />\r\n\r\n</LinearLayout>"
  },
  {
    "path": "push_demo/res/layout/hwpush_collect_tip_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"wrap_content\"\r\n    android:orientation=\"vertical\" \r\n    android:paddingLeft=\"15dp\"\r\n    android:paddingRight=\"15dp\"\r\n    android:paddingBottom=\"26dip\">\r\n    \r\n    <RelativeLayout\r\n            android:id=\"@+id/hwpush_collect_tip_layout\"\r\n            android:layout_width=\"match_parent\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginTop=\"27dip\"\r\n            android:gravity=\"center\" >\r\n\r\n            <ImageView\r\n                android:id=\"@+id/hwpush_bt_collect_tip_img\"\r\n                android:layout_width=\"match_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:gravity=\"center\"\r\n                android:layout_marginTop=\"8dp\"\r\n                android:clickable=\"false\"\r\n                android:contentDescription=\"@string/hwpush_msg_favorites\"\r\n                android:src=\"@drawable/hwpush_main_icon\" />\r\n\r\n            <TextView\r\n                android:id=\"@+id/hwpush_bt_selectall_txt\"\r\n                android:layout_width=\"match_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_below=\"@+id/hwpush_bt_collect_tip_img\"\r\n                android:layout_marginTop=\"5dip\"\r\n                android:gravity=\"center\"\r\n                android:text=\"@string/hwpush_msg_favorites\"\r\n                android:textSize=\"13sp\" />\r\n        </RelativeLayout>\r\n\r\n    <TextView\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"wrap_content\" \r\n        android:layout_marginTop=\"9dp\"\r\n        android:layout_marginLeft=\"5dp\"\r\n        android:layout_marginRight=\"5dp\"\r\n        android:textSize=\"13sp\"\r\n        android:text=\"@string/hwpush_collect_tip\"/>\r\n    \r\n</LinearLayout>\r\n"
  },
  {
    "path": "push_demo/res/layout/hwpush_collection_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:layout_width=\"wrap_content\"\r\n    android:layout_height=\"64dip\"\r\n    android:orientation=\"vertical\"\r\n    android:padding=\"8dip\"\r\n    >\r\n\r\n    <ImageView\r\n        android:id=\"@+id/hwpush_favicon\"\r\n        android:scaleType=\"centerCrop\"\r\n        android:layout_height=\"36dip\"\r\n        android:layout_width=\"36dip\"\r\n        android:layout_gravity=\"center_vertical\"\r\n        android:layout_centerVertical=\"true\"\r\n        android:layout_alignParentLeft=\"true\"\r\n        android:focusable=\"false\"\r\n        android:visibility=\"visible\"\r\n        android:layout_marginLeft=\"4dip\"\r\n        />\r\n    \r\n    <CheckBox android:id=\"@+id/hwpush_delCheck\"\r\n        android:layout_width=\"32dip\"\r\n        android:layout_height=\"32dip\"\r\n        android:layout_alignParentRight=\"true\"\r\n        android:layout_centerVertical=\"true\"\r\n        android:focusable=\"false\"\r\n        android:button=\"@drawable/hwpush_btn_checkbox_list_star\"\r\n        android:gravity=\"center_vertical\"\r\n        android:visibility=\"invisible\"\r\n        android:clickable=\"false\"\r\n        />\r\n    \r\n    <LinearLayout \r\n        android:id=\"@+id/hwpush_selfshowmsg_layout\"\r\n        android:layout_width=\"wrap_content\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:paddingLeft=\"12dip\"\r\n        android:paddingRight=\"8dip\"\r\n        android:gravity=\"center_vertical\"\r\n        android:orientation=\"vertical\"\r\n        android:layout_toRightOf=\"@id/hwpush_favicon\"\r\n        android:layout_toLeftOf=\"@id/hwpush_delCheck\">\r\n        \r\n        <TextView android:id=\"@+id/hwpush_selfshowmsg_title\"\r\n\t        android:layout_height=\"wrap_content\"\r\n\t        android:layout_width=\"wrap_content\"\r\n\t        android:gravity=\"center_vertical\"\r\n\t        android:singleLine=\"true\"\r\n\t        android:ellipsize=\"end\"\r\n\t        android:typeface=\"sans\"\r\n\t        android:textSize=\"14sp\"\r\n\t        android:textColor=\"@color/hwpush_text_color_snapshot_title\"\r\n\t        android:focusable=\"false\"\r\n\t        />\r\n        \r\n\t    <TextView android:id=\"@+id/hwpush_selfshowmsg_content\"\r\n\t        android:layout_width=\"wrap_content\"\r\n\t        android:layout_height=\"wrap_content\"\r\n\t        android:gravity=\"center_vertical\"\r\n\t        android:maxLines=\"1\"\r\n\t        android:singleLine=\"true\"\r\n\t        android:ellipsize=\"end\"\r\n\t        android:typeface=\"sans\"\r\n\t        android:textSize=\"11dp\"\r\n\t        android:textColor=\"@color/hwpush_text_color_history_url\"\r\n\t        android:focusable=\"false\"\r\n\t        />\r\n        \r\n    </LinearLayout>\r\n    \r\n</RelativeLayout>\r\n"
  },
  {
    "path": "push_demo/res/layout/hwpush_collection_listview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:tools=\"http://schemas.android.com/tools\"\r\n    android:id=\"@+id/listview_layout\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"match_parent\" >\r\n\r\n    <LinearLayout\r\n        android:id=\"@+id/hwpush_bottom_bar\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"59dip\"\r\n        android:layout_alignParentBottom=\"true\"\r\n        android:layout_gravity=\"bottom\"\r\n        android:background=\"@drawable/hwpush_ab_bottom_emui\"\r\n        android:gravity=\"center\"\r\n        android:orientation=\"horizontal\"\r\n        android:visibility=\"gone\" >\r\n\r\n        <View\r\n            android:id=\"@+id/hwpush_bt_line_0\"\r\n            android:layout_width=\"110dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_gravity=\"bottom\"\r\n            android:layout_marginLeft=\"0dip\"\r\n            android:background=\"@color/hwpush_url_line_color\"\r\n            android:visibility=\"visible\" />\r\n\r\n        <!-- delete -->\r\n        <RelativeLayout\r\n            android:id=\"@+id/hwpush_bottombar_delete_layout\"\r\n            android:layout_width=\"106dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginLeft=\"2dip\"\r\n            android:gravity=\"center\" >\r\n\r\n            <ImageView\r\n                android:id=\"@+id/hwpush_bt_delete_img\"\r\n                android:layout_width=\"wrap_content\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_centerHorizontal=\"true\"\r\n                android:clickable=\"true\"\r\n                android:layout_marginTop=\"8dp\"\r\n                android:contentDescription=\"@string/hwpush_delete\"\r\n                android:src=\"@drawable/hwpush_ic_toolbar_delete\" />\r\n\r\n            <TextView\r\n                android:id=\"@+id/hwpush_bt_delete_txt\"\r\n                android:layout_width=\"fill_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_below=\"@+id/hwpush_bt_delete_img\"\r\n                android:layout_marginTop=\"1dip\"\r\n                android:gravity=\"center\"\r\n                android:text=\"@string/hwpush_delete\"\r\n                android:textColor=\"@color/hwpush_bt_txt_nor\"\r\n                android:textSize=\"9sp\" />\r\n        </RelativeLayout>\r\n\r\n        <View\r\n            android:id=\"@+id/hwpush_bt_line_3\"\r\n            android:layout_width=\"2dip\"\r\n            android:layout_height=\"30dip\"\r\n            android:layout_gravity=\"bottom\"\r\n            android:background=\"@color/hwpush_url_line_color\"\r\n            android:visibility=\"visible\" />\r\n        \r\n        <!-- select all -->\r\n        <RelativeLayout\r\n            android:id=\"@+id/hwpush_bottombar_selectall_layout\"\r\n            android:layout_width=\"106dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginLeft=\"2dip\"\r\n            android:gravity=\"center\" >\r\n\r\n            <ImageView\r\n                android:id=\"@+id/hwpush_bt_selectall_img\"\r\n                android:layout_width=\"wrap_content\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_centerHorizontal=\"true\"\r\n                android:layout_marginTop=\"8dp\"\r\n                android:clickable=\"true\"\r\n                android:contentDescription=\"@string/hwpush_selectall\"\r\n                android:background=\"@drawable/hwpush_ic_toolbar_multiple\" />\r\n\r\n            <TextView\r\n                android:id=\"@+id/hwpush_bt_selectall_txt\"\r\n                android:layout_width=\"fill_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_below=\"@+id/hwpush_bt_selectall_img\"\r\n                android:layout_marginTop=\"1dip\"\r\n                android:gravity=\"center\"\r\n                android:text=\"@string/hwpush_selectall\"\r\n                android:textColor=\"@color/hwpush_bt_txt_nor\"\r\n                android:textSize=\"9sp\" />\r\n        </RelativeLayout>\r\n\r\n        <View\r\n            android:id=\"@+id/hwpush_bt_line_4\"\r\n            android:layout_width=\"110dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_gravity=\"bottom\"\r\n            android:background=\"@color/hwpush_url_line_color\"\r\n            android:visibility=\"visible\" />\r\n    </LinearLayout>\r\n\r\n    <com.huawei.android.pushselfshow.utils.RelativeLayoutForBckgColor\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"48dp\"\r\n        android:layout_alignParentTop=\"true\"\r\n        android:paddingLeft=\"10dp\"\r\n        android:paddingRight=\"10dp\"\r\n        android:id=\"@+id/hwpush_titlebar\">\r\n\r\n        <ImageView\r\n            android:id=\"@+id/hwpush_bt_delete\"\r\n            android:layout_width=\"wrap_content\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_centerVertical=\"true\"\r\n            android:clickable=\"true\"\r\n            android:layout_marginBottom=\"8dp\"\r\n            android:layout_alignParentLeft=\"true\"\r\n            android:src=\"@drawable/hwpush_ic_cancel\" \r\n            android:visibility=\"invisible\" />\r\n\r\n        <ImageView\r\n            android:id=\"@+id/hwpush_bt_ok\"\r\n            android:layout_width=\"wrap_content\"\r\n            android:layout_alignParentRight=\"true\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_centerVertical=\"true\"\r\n            android:clickable=\"true\"\r\n            android:layout_marginBottom=\"8dp\"\r\n            android:src=\"@drawable/hwpush_ic_ok\" \r\n            android:visibility=\"invisible\" />\r\n        \r\n        <LinearLayout \r\n            android:layout_width=\"match_parent\"\r\n            android:layout_height=\"match_parent\"\r\n            android:layout_toRightOf=\"@id/hwpush_bt_delete\"\r\n            android:layout_toLeftOf=\"@id/hwpush_bt_ok\"\r\n            android:gravity=\"center\"\r\n            android:orientation=\"horizontal\">\r\n            \r\n            <TextView\r\n                android:id=\"@+id/hwpush_txt_delitem\"\r\n\t            android:gravity=\"center\"\r\n\t            android:layout_height=\"wrap_content\"\r\n\t            android:layout_width=\"wrap_content\"\r\n\t            android:text=\"@string/hwpush_msg_favorites\"\r\n\t            android:textColor=\"@color/hwpush_white\"\r\n\t            android:textSize=\"14sp\" />\r\n\r\n\t        <TextView\r\n\t            android:id=\"@+id/hwpush_txt_delnum\"\r\n\t            android:gravity=\"center\"\r\n\t            android:layout_height=\"wrap_content\"\r\n\t            android:layout_width=\"wrap_content\"\r\n\t            android:textColor=\"@color/hwpush_white\"\r\n\t            android:layout_marginLeft=\"2dp\"\r\n\t            android:textSize=\"10dp\" \r\n\t            android:background=\"@drawable/hwpush_pic_ab_number\"\r\n\t            android:visibility=\"gone\" />\r\n            \r\n        </LinearLayout>\r\n        \r\n         \r\n    </com.huawei.android.pushselfshow.utils.RelativeLayoutForBckgColor>\r\n\r\n    <ListView\r\n        android:id=\"@+id/hwpush_collection_list\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"match_parent\"\r\n        android:layout_above=\"@id/hwpush_bottom_bar\"\r\n        android:layout_below=\"@id/hwpush_titlebar\"\r\n        android:layout_gravity=\"center_horizontal\"\r\n        android:scrollbarStyle=\"outsideOverlay\" \r\n        android:background=\"@drawable/hwpush_background_emui\">\r\n    </ListView>\r\n    <LinearLayout\r\n        android:id=\"@+id/hwpush_no_collection_view\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"match_parent\"\r\n        android:layout_above=\"@id/hwpush_bottom_bar\"\r\n        android:layout_below=\"@id/hwpush_titlebar\"\r\n        android:orientation=\"vertical\"\r\n        android:visibility=\"gone\">\r\n        <ImageView \r\n            android:id=\"@+id/hwpush_no_collection_icon\"\r\n            android:layout_width=\"70dip\"\r\n            android:layout_height=\"70dip\"\r\n            android:layout_marginTop=\"160dip\"\r\n            android:layout_gravity=\"center_horizontal\"\r\n            android:src=\"@drawable/hwpush_no_collection\"/>\r\n        <TextView\r\n            android:id=\"@+id/hwpush_no_collection_text\"\r\n            android:layout_width=\"match_parent\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginTop=\"3dip\"\r\n            android:layout_marginLeft=\"28dip\"\r\n            android:layout_marginRight=\"28dip\"\r\n            android:layout_gravity=\"center_horizontal\"\r\n            android:gravity=\"center_horizontal\"\r\n            android:text=\"@string/hwpush_no_collection\"\r\n            android:textSize=\"12sp\"\r\n            android:alpha=\"0.65\"\r\n            android:textColor=\"@color/hwpush_black_color\"/>\r\n    </LinearLayout>\r\n\r\n</RelativeLayout>"
  },
  {
    "path": "push_demo/res/layout/hwpush_icons_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"match_parent\"\r\n    android:layout_marginRight=\"4dp\"\r\n    android:orientation=\"vertical\" >\r\n    <ImageView \r\n        android:id=\"@+id/smallicon\"\r\n        android:layout_width=\"18dip\"\r\n        android:layout_height=\"18dip\"\r\n        android:adjustViewBounds=\"true\"\r\n        />\r\n\r\n</LinearLayout>"
  },
  {
    "path": "push_demo/res/layout/hwpush_layout2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:internal=\"http://schemas.android.com/apk/prv/res/android\"\r\n    android:id=\"@+id/status_bar_latest_event_content\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"64dp\">\r\n\r\n    <ImageView\r\n        android:id=\"@+id/icon\"\r\n        android:layout_width=\"@android:dimen/notification_large_icon_width\"\r\n        android:layout_height=\"@android:dimen/notification_large_icon_height\"\r\n        android:layout_gravity=\"center_vertical\"\r\n        android:scaleType=\"center\" />\r\n\r\n    <LinearLayout\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:layout_gravity=\"fill_vertical\"\r\n        android:layout_marginLeft=\"@android:dimen/notification_large_icon_width\"\r\n        android:gravity=\"center_vertical\"\r\n        android:minHeight=\"@android:dimen/notification_large_icon_height\"\r\n        android:orientation=\"horizontal\"\r\n        android:paddingBottom=\"13dp\"\r\n        android:paddingRight=\"8dp\"\r\n        android:paddingTop=\"8dp\" >\r\n\r\n        <LinearLayout\r\n            android:id=\"@+id/line1\"\r\n            android:layout_width=\"match_parent\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginLeft=\"8dp\"\r\n            android:layout_weight=\"1\"\r\n            android:orientation=\"vertical\"\r\n            android:paddingTop=\"6dp\" >\r\n\r\n            <TextView\r\n                android:id=\"@+id/title\"\r\n                android:layout_width=\"match_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:ellipsize=\"end\"\r\n                android:fadingEdge=\"horizontal\"\r\n                android:singleLine=\"true\"\r\n                android:textAppearance=\"@android:style/TextAppearance.StatusBar.EventContent.Title\"\r\n                android:textColor=\"#d8000000\"\r\n                android:textSize=\"14sp\" />\r\n\r\n            <TextView\r\n                android:id=\"@+id/text\"\r\n                android:layout_width=\"match_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_gravity=\"center\"\r\n                android:layout_marginTop=\"3dip\"\r\n                android:ellipsize=\"end\"\r\n                android:fadingEdge=\"horizontal\"\r\n                android:singleLine=\"true\"\r\n                android:textAppearance=\"@android:style/TextAppearance.StatusBar.EventContent\"\r\n                android:textColor=\"#a5000000\"\r\n                android:textSize=\"11dp\" />\r\n        </LinearLayout>\r\n\r\n        <Button\r\n            android:id=\"@+id/right_btn\"\r\n            android:layout_width=\"70dip\"\r\n            android:layout_height=\"32dip\"\r\n            android:textSize=\"11dp\"\r\n            android:layout_marginBottom=\"1dip\"\r\n            android:layout_marginLeft=\"4dip\"\r\n            android:layout_marginTop=\"6dip\"\r\n            android:layout_weight=\"0\"\r\n            android:ellipsize=\"end\"\r\n            android:singleLine=\"true\"\r\n            android:visibility=\"gone\" />\r\n    </LinearLayout>\r\n\r\n</FrameLayout>"
  },
  {
    "path": "push_demo/res/layout/hwpush_layout4.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:internal=\"http://schemas.android.com/apk/prv/res/android\"\r\n    android:id=\"@+id/status_bar_latest_event_content\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"64dp\">\r\n\r\n    <ImageView\r\n        android:id=\"@+id/icon\"\r\n        android:layout_width=\"@android:dimen/notification_large_icon_width\"\r\n        android:layout_height=\"@android:dimen/notification_large_icon_height\"\r\n        android:layout_gravity=\"center_vertical\"\r\n        android:scaleType=\"center\" />\r\n\r\n    <LinearLayout\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:layout_gravity=\"fill_vertical\"\r\n        android:layout_marginLeft=\"@android:dimen/notification_large_icon_width\"\r\n        android:minHeight=\"@android:dimen/notification_large_icon_height\"\r\n        android:orientation=\"horizontal\"\r\n        android:paddingBottom=\"13dp\"\r\n        android:paddingRight=\"8dp\"\r\n        android:paddingTop=\"8dp\" >\r\n\r\n        <LinearLayout\r\n            android:id=\"@+id/line1\"\r\n            android:layout_width=\"match_parent\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginLeft=\"8dp\"\r\n            android:layout_weight=\"1\"\r\n            android:orientation=\"vertical\"\r\n            android:paddingTop=\"6dp\" >\r\n\r\n            <TextView\r\n                android:id=\"@+id/title\"\r\n                android:layout_width=\"match_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:ellipsize=\"marquee\"\r\n                android:fadingEdge=\"horizontal\"\r\n                android:singleLine=\"true\"\r\n                android:textAppearance=\"@android:style/TextAppearance.StatusBar.EventContent.Title\" \r\n                android:textSize=\"14sp\"\r\n                android:textColor=\"#d8000000\"/>\r\n\r\n            <LinearLayout\r\n                android:id=\"@+id/line3\"\r\n                android:layout_width=\"match_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:gravity=\"center_vertical\"\r\n                android:orientation=\"horizontal\" >\r\n\r\n                <LinearLayout\r\n                    android:id=\"@+id/linear_icons\"\r\n                    android:layout_width=\"wrap_content\"\r\n                    android:layout_height=\"wrap_content\"\r\n                    android:orientation=\"horizontal\" >\r\n                </LinearLayout>\r\n            </LinearLayout>\r\n        </LinearLayout>\r\n\r\n        <Button\r\n            android:id=\"@+id/right_btn\"\r\n            android:layout_width=\"70dip\"\r\n            android:layout_height=\"32dip\"\r\n            android:textSize=\"11dp\"\r\n            android:layout_marginBottom=\"1dip\"\r\n            android:layout_marginLeft=\"4dip\"\r\n            android:layout_marginTop=\"6dip\"\r\n            android:layout_weight=\"0\"\r\n            android:singleLine=\"true\"\r\n            android:ellipsize=\"end\"\r\n            android:visibility=\"gone\" />\r\n    </LinearLayout>\r\n\r\n</FrameLayout>"
  },
  {
    "path": "push_demo/res/layout/hwpush_layout7.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n\r\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:internal=\"http://schemas.android.com/apk/prv/res/android\"\r\n    android:id=\"@+id/status_bar_latest_event_content\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"64dp\"\r\n    >\r\n    <ImageView android:id=\"@+id/icon\"\r\n        android:layout_width=\"@android:dimen/notification_large_icon_width\"\r\n        android:layout_height=\"@android:dimen/notification_large_icon_height\"\r\n        android:layout_gravity=\"center_vertical\"\r\n        android:scaleType=\"center\"\r\n        />\r\n    <LinearLayout\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:layout_gravity=\"fill_vertical\"\r\n        android:layout_marginLeft=\"@android:dimen/notification_large_icon_width\"\r\n        android:minHeight=\"@android:dimen/notification_large_icon_height\"\r\n        android:orientation=\"horizontal\"\r\n        android:paddingBottom=\"13dp\"\r\n        android:paddingTop=\"8dp\" \r\n        android:baselineAligned=\"false\">\r\n    <LinearLayout\r\n        android:id=\"@+id/line1\"\r\n        android:layout_width=\"85dip\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:layout_weight=\"1\"\r\n        android:layout_marginLeft=\"8dip\"\r\n        android:gravity=\"top\"\r\n        android:minHeight=\"@android:dimen/notification_large_icon_height\"\r\n        android:orientation=\"vertical\"\r\n        android:paddingTop=\"6dp\" >\r\n\r\n        <TextView\r\n            android:id=\"@+id/title\"\r\n            android:textAppearance=\"@android:style/TextAppearance.StatusBar.EventContent.Title\"\r\n            android:layout_width=\"match_parent\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:ellipsize=\"end\"\r\n            android:fadingEdge=\"horizontal\"\r\n            android:singleLine=\"true\"\r\n            android:textSize=\"14sp\"\r\n            android:textColor=\"#d8000000\"/>\r\n\r\n        <TextView\r\n            android:id=\"@+id/text\"\r\n            android:layout_width=\"match_parent\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginTop=\"3dip\"\r\n            android:ellipsize=\"end\"\r\n            android:fadingEdge=\"horizontal\"\r\n            android:singleLine=\"true\"\r\n            android:textAppearance=\"@android:style/TextAppearance.StatusBar.EventContent\"\r\n            android:textSize=\"11dp\"\r\n            android:textColor=\"#a5000000\"/>\r\n    </LinearLayout>\r\n    <LinearLayout\r\n        android:id=\"@+id/line3\"\r\n        android:layout_width=\"wrap_content\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:layout_weight=\"0\"\r\n        android:layout_marginLeft=\"8dip\"\r\n        android:layout_gravity=\"center_vertical\"\r\n        android:gravity=\"center_vertical\"\r\n        android:orientation=\"horizontal\" >\r\n\r\n        <LinearLayout\r\n            android:id=\"@+id/linear_buttons\"\r\n            android:layout_width=\"wrap_content\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:orientation=\"horizontal\" >\r\n        </LinearLayout>\r\n    </LinearLayout>\r\n    </LinearLayout>\r\n</FrameLayout>\r\n"
  },
  {
    "path": "push_demo/res/layout/hwpush_layout8.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n\r\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:internal=\"http://schemas.android.com/apk/prv/res/android\"\r\n    android:id=\"@+id/status_bar_latest_event_content\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"wrap_content\"\r\n    android:paddingRight=\"8dip\"\r\n    >\r\n    <ImageView android:id=\"@+id/big_pic\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"match_parent\"\r\n        android:scaleType=\"centerCrop\"\r\n        android:clickable=\"false\"\r\n        />\r\n</FrameLayout>\r\n"
  },
  {
    "path": "push_demo/res/layout/hwpush_msg_show.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:tools=\"http://schemas.android.com/tools\"\r\n    android:id=\"@+id/listview_layout\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"match_parent\" >\r\n\r\n    <LinearLayout\r\n        android:id=\"@+id/hwpush_bottom_bar\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"59dip\"\r\n        android:layout_alignParentBottom=\"true\"\r\n        android:layout_gravity=\"bottom\"\r\n        android:background=\"@drawable/hwpush_ab_bottom_emui\"\r\n        android:gravity=\"center\"\r\n        android:orientation=\"horizontal\"\r\n        android:visibility=\"visible\" >\r\n\r\n        <View\r\n            android:id=\"@+id/hwpush_bt_line_0\"\r\n            android:layout_width=\"74dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_gravity=\"bottom\"\r\n            android:layout_marginLeft=\"0dip\"\r\n            android:background=\"@color/hwpush_url_line_color\"\r\n            android:visibility=\"visible\" />\r\n        <!-- back -->\r\n\r\n        <RelativeLayout\r\n            android:id=\"@+id/hwpush_bottombar_backward_layout\"\r\n            android:layout_width=\"58dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:gravity=\"center\" >\r\n\r\n            <ImageView\r\n                android:id=\"@+id/hwpush_bt_back_img\"\r\n                android:layout_width=\"wrap_content\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_centerHorizontal=\"true\"\r\n                android:clickable=\"true\"\r\n                android:contentDescription=\"@string/hwpush_goback\"\r\n                android:src=\"@drawable/hwpush_ic_toolbar_back\" />\r\n\r\n            <TextView\r\n                android:id=\"@+id/hwpush_bt_back_txt\"\r\n                android:layout_width=\"fill_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_below=\"@+id/hwpush_bt_back_img\"\r\n                android:layout_marginTop=\"1dip\"\r\n                android:gravity=\"center\"\r\n                android:text=\"@string/hwpush_goback\"\r\n                android:textColor=\"@color/hwpush_bt_txt_nor\"\r\n                android:textSize=\"9sp\" />\r\n        </RelativeLayout>\r\n\r\n        <View\r\n            android:id=\"@+id/hwpush_bt_line_1\"\r\n            android:layout_width=\"2dip\"\r\n            android:layout_height=\"30dip\"\r\n            android:layout_gravity=\"bottom\"\r\n            android:background=\"@color/hwpush_url_line_color\"\r\n            android:visibility=\"visible\" />\r\n        <!-- forward -->\r\n\r\n        <RelativeLayout\r\n            android:id=\"@+id/hwpush_bottombar_forward_layout\"\r\n            android:layout_width=\"58dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginLeft=\"2dip\"\r\n            android:gravity=\"center\" >\r\n\r\n            <ImageView\r\n                android:id=\"@+id/hwpush_bt_forward_img\"\r\n                android:layout_width=\"wrap_content\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_centerHorizontal=\"true\"\r\n                android:clickable=\"true\"\r\n                android:contentDescription=\"@string/hwpush_forward\"\r\n                android:src=\"@drawable/hwpush_ic_toolbar_advance\" />\r\n\r\n            <TextView\r\n                android:id=\"@+id/hwpush_bt_forward_txt\"\r\n                android:layout_width=\"fill_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_below=\"@+id/hwpush_bt_forward_img\"\r\n                android:layout_marginTop=\"1dip\"\r\n                android:gravity=\"center\"\r\n                android:text=\"@string/hwpush_forward\"\r\n                android:textColor=\"@color/hwpush_bt_txt_nor\"\r\n                android:textSize=\"9sp\" />\r\n        </RelativeLayout>\r\n\r\n        <View\r\n            android:id=\"@+id/hwpush_bt_line_2\"\r\n            android:layout_width=\"2dip\"\r\n            android:layout_height=\"30dip\"\r\n            android:layout_gravity=\"bottom\"\r\n            android:background=\"@color/hwpush_url_line_color\"\r\n            android:visibility=\"visible\" />\r\n        <!-- refresh -->\r\n\r\n        <RelativeLayout\r\n            android:id=\"@+id/hwpush_bottombar_refresh_layout\"\r\n            android:layout_width=\"58dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginLeft=\"2dip\"\r\n            android:gravity=\"center\" >\r\n\r\n            <ImageView\r\n                android:id=\"@+id/hwpush_bt_refresh_img\"\r\n                android:layout_width=\"wrap_content\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_centerHorizontal=\"true\"\r\n                android:clickable=\"true\"\r\n                android:contentDescription=\"@string/hwpush_refresh\"\r\n                android:src=\"@drawable/hwpush_ic_toolbar_refresh\" />\r\n\r\n            <TextView\r\n                android:id=\"@+id/hwpush_bt_refresh_txt\"\r\n                android:layout_width=\"fill_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_below=\"@+id/hwpush_bt_refresh_img\"\r\n                android:layout_marginTop=\"1dip\"\r\n                android:gravity=\"center\"\r\n                android:text=\"@string/hwpush_refresh\"\r\n                android:textColor=\"@color/hwpush_bt_txt_nor\"\r\n                android:textSize=\"9sp\" />\r\n        </RelativeLayout>\r\n\r\n        <View\r\n            android:id=\"@+id/hwpush_bt_line_3\"\r\n            android:layout_width=\"2dip\"\r\n            android:layout_height=\"30dip\"\r\n            android:layout_gravity=\"bottom\"\r\n            android:background=\"@color/hwpush_url_line_color\"\r\n            android:visibility=\"visible\" />\r\n        <!-- collect -->\r\n\r\n        <RelativeLayout\r\n            android:id=\"@+id/hwpush_bottombar_collect_layout\"\r\n            android:layout_width=\"58dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginLeft=\"2dip\"\r\n            android:gravity=\"center\" >\r\n\r\n            <ImageView\r\n                android:id=\"@+id/hwpush_bt_collect_img\"\r\n                android:layout_width=\"wrap_content\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_centerHorizontal=\"true\"\r\n                android:clickable=\"true\"\r\n                android:contentDescription=\"@string/hwpush_collect\"\r\n                android:src=\"@drawable/hwpush_ic_toolbar_collect\" />\r\n\r\n            <TextView\r\n                android:id=\"@+id/hwpush_bt_collect_txt\"\r\n                android:layout_width=\"fill_parent\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_below=\"@+id/hwpush_bt_collect_img\"\r\n                android:layout_marginTop=\"1dip\"\r\n                android:gravity=\"center\"\r\n                android:text=\"@string/hwpush_collect\"\r\n                android:textColor=\"@color/hwpush_bt_txt_nor\"\r\n                android:textSize=\"9sp\" />\r\n        </RelativeLayout>\r\n\r\n        <View\r\n            android:id=\"@+id/hwpush_bt_line_4\"\r\n            android:layout_width=\"74dip\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_gravity=\"bottom\"\r\n            android:background=\"@color/hwpush_url_line_color\"\r\n            android:visibility=\"visible\" />\r\n    </LinearLayout>\r\n\r\n    <com.huawei.android.pushselfshow.utils.RelativeLayoutForBckgColor\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"48dp\"\r\n        android:paddingLeft=\"12dp\"\r\n        android:paddingRight=\"12dp\"\r\n        android:layout_alignParentTop=\"true\"\r\n        android:id=\"@+id/hwpush_titlebar\">\r\n\r\n        <TextView\r\n        \tandroid:id=\"@+id/hwpush_msg_title\"\r\n           \tandroid:layout_height=\"match_parent\"\r\n           \tandroid:layout_width=\"match_parent\"\r\n           \tandroid:textColor=\"@color/hwpush_white\"\r\n            android:text=\"@string/hwpush_richmedia\"\r\n           \tandroid:gravity=\"center\"\r\n\t        android:singleLine=\"true\"\r\n\t        android:ellipsize=\"end\"\r\n           \tandroid:textSize=\"14sp\" />\r\n    </com.huawei.android.pushselfshow.utils.RelativeLayoutForBckgColor>\r\n    <com.huawei.android.pushselfshow.richpush.html.PageProgressView\r\n        android:id=\"@+id/hwpush_progressbar\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:background=\"@null\"\r\n        android:layout_below=\"@+id/hwpush_titlebar\"\r\n        android:src=\"@drawable/hwpush_progress\"\r\n        android:layout_marginTop=\"-11dip\"\r\n        android:visibility=\"invisible\" />\r\n    <WebView\r\n        android:id=\"@+id/hwpush_msg_show_view\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"match_parent\"\r\n        android:layout_above=\"@id/hwpush_bottom_bar\"\r\n        android:layout_below=\"@id/hwpush_progressbar\"\r\n        android:layout_gravity=\"center_horizontal\"\r\n        android:scrollbarStyle=\"outsideOverlay\" \r\n        android:background=\"@drawable/hwpush_list_activated_emui\">\r\n    </WebView>\r\n\r\n</RelativeLayout>"
  },
  {
    "path": "push_demo/res/layout/hwpush_titlebar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<com.huawei.pushui.RelativeLayoutForBckgColor xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"48dp\" >\r\n    \r\n    <ImageView\r\n                android:id=\"@+id/hwpush_bt_delete\"\r\n                android:layout_width=\"wrap_content\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_centerHorizontal=\"true\"\r\n                android:clickable=\"true\"\r\n                android:contentDescription=\"@string/hwpush_refresh\"\r\n                android:src=\"@drawable/hwpush_ic_toolbar_refresh\" \r\n                android:layout_toLeftOf=\"@+id/hwpush_title\"/>\r\n    \r\n    <ImageView\r\n                android:id=\"@+id/hwpush_bt_ok\"\r\n                android:layout_width=\"wrap_content\"\r\n                android:layout_height=\"wrap_content\"\r\n                android:layout_centerHorizontal=\"true\"\r\n                android:clickable=\"true\"\r\n                android:contentDescription=\"@string/hwpush_refresh\"\r\n                android:src=\"@drawable/hwpush_ic_toolbar_refresh\" \r\n                android:layout_toRightOf=\"@+id/hwpush_title\"/>\r\n    \r\n    <TextView\r\n                android:id=\"@+id/hwpush_title\"\r\n                android:layout_width=\"match_parent\"\r\n                android:layout_height=\"match_parent\"\r\n                android:layout_marginTop=\"1dip\"\r\n                android:gravity=\"center\"\r\n                android:text=\"@string/hwpush_refresh\"\r\n                android:textColor=\"@color/hwpush_bt_txt_nor\"\r\n                android:textSize=\"9sp\" />\r\n\r\n</com.huawei.pushui.RelativeLayoutForBckgColor>\r\n"
  },
  {
    "path": "push_demo/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"common_bg\">#fcfcfc</color>\n    <color name=\"common_header_blue\">#00abf1</color>\n    <color name=\"view_focused\">#11000000</color>\n    <color name=\"view_pressed\">@color/view_focused</color>\n    <color name=\"btn_login_normal\">#2dafa3</color>\n    <color name=\"btn_login_pressed\">@color/common_header_blue</color>\n    <color name=\"btn_gray_normal\">#c0c0c0</color>\n    <color name=\"bottom_bar_normal_bg\">#2D2F31</color>\n    <color name=\"common_botton_bar_blue\">#2ea7e0</color>\n    <color name=\"common_bottom_bar_normal_bg\">#2d2f31</color>\n    <color name=\"common_bottom_bar_selected_bg\">#161718</color>\n    <color name=\"divider_list\">#cccccc</color>\n    <color name=\"blue_337aba\">#337aba</color>\n    <color name=\"setting_item_pressed\">@color/btn_login_pressed</color>\n    <color name=\"setting_divider\">#e6e6e6</color>\n    <color name=\"gray_33\">#333333</color>\n    <color name=\"bg_chat\">#f2f0eb</color>\n    <color name=\"gray_normal\">#666667</color>\n\n    <color name=\"white_trans_40\">#66FFFFFF</color>\n\n\n</resources>\n"
  },
  {
    "path": "push_demo/res/values/dimen_font.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- 一般不用h1和h6, header标题h2, catalog标题h3, tab名称h4, 图标文字h5 -->\n    <dimen name=\"large\">26sp</dimen>\n    <dimen name=\"h0\">24sp</dimen>\n    <dimen name=\"h1\">22sp</dimen>\n    <dimen name=\"h2\">20sp</dimen>\n    <dimen name=\"h3\">18sp</dimen>\n    <dimen name=\"h4\">16sp</dimen>\n    <dimen name=\"h5\">14sp</dimen>\n    <dimen name=\"h6\">12sp</dimen>\n    <dimen name=\"h7\">10sp</dimen>\n</resources>"
  },
  {
    "path": "push_demo/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n    <dimen name=\"header_common_height\">46dp</dimen>\n    <dimen name=\"header_button_width\">48dp</dimen>\n\n    <!--setting-->\n    <dimen name=\"setting_left_padding\">20dp</dimen>\n    <dimen name=\"setting_right_padding\">20dp</dimen>\n    <dimen name=\"setting_item_height\">47dp</dimen>\n    <dimen name=\"setting_item_left_padding\">27dp</dimen>\n    <dimen name=\"setting_item_right_padding\">27dp</dimen>\n    <dimen name=\"setting_item_checkbox_right_padding\">32dp</dimen>\n    <dimen name=\"setting_checkbox_right_margin\">14dp</dimen>\n\n\n    <dimen name=\"margin_chat_activity\">5dp</dimen>\n    <dimen name=\"size_avatar\">50dp</dimen>\n</resources>\n"
  },
  {
    "path": "push_demo/res/values/hwpush_colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"hwpush_white\">#ffffffff</color>\n    <color name=\"hwpush_black\">#ff000000</color>\n    <color name=\"hwpush_url_line_color\">#20ffffff</color>\n    <color name=\"hwpush_text_color_snapshot_title\">#D8000000</color>\n    <color name=\"hwpush_text_color_history_url\">#A5000000</color>\n    <color name=\"hwpush_bt_txt_nor\">#65000000</color>\n    <color name=\"hwpush_bt_txt_disable\">#20000000</color>\n    <color name=\"hwpush_bt_txt_touch\">#33000000</color>\n    <color name=\"hwpush_bgcolor\">#2c9195</color>\n\t<color name=\"hwpush_warn_color\">#ffd43e25</color>\n    <color name=\"hwpush_select_color\">#3fc0c5</color>\n    <color name=\"hwpush_black_color\">#000000</color>\n</resources>\n"
  },
  {
    "path": "push_demo/res/values/hwpush_strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n    <string name=\"hwpush_goback\">Back</string>\n    <string name=\"hwpush_forward\">Forward</string>\n    <string name=\"hwpush_refresh\">Refresh</string>\n    <string name=\"hwpush_collect\">Favorite</string>\n    <string name=\"hwpush_deltitle\">Delete notifications</string>\n    <string name=\"hwpush_msg_collect\">Favorites</string>\n    <string name=\"hwpush_msg_favorites\">Favorite notifications</string>\n    \n    <string name=\"hwpush_delete\">Delete</string>\n    <string name=\"hwpush_selectall\">Select all</string>\n    \n    <string name=\"hwpush_collect_tip\">A Favorites icon will be created on the home screen. You can touch the icon to view all your favorite notifications.</string>\n    <string name=\"hwpush_collect_tip_known\">OK</string>\n    \n    <string name=\"hwpush_cancel\">Cancel</string>\n    <plurals name=\"hwpush_delete_tip\">\n        <!--  The example of %d is 1-->\n        <item quantity=\"one\">Delete selected notification?</item>\n        <!--  The example of %d is 11-->\n        <item quantity=\"other\">Delete selected notifications?</item>\n    </plurals>\n    <string name=\"hwpush_unselectall\">Deselect all</string>\n    <string name=\"hwpush_no_collection\">No favorite notifications</string>\n    <string name=\"cloudpush_app_name\">Push Service</string>\n    <string name=\"hwpush_richmedia\">Rich media</string>\n    <string name=\"hwpush_loading_title\">Loading... Please wait.</string>\n</resources>"
  },
  {
    "path": "push_demo/res/values/hwpush_styles.xml",
    "content": "<resources>\r\n\r\n    <style name=\"hwpush_NoActionBar\" parent=\"android:Theme.Holo.Light\">\r\n        <item name=\"android:windowActionBar\">false</item>\r\n        <item name=\"android:windowNoTitle\">true</item>\r\n    </style>\r\n    \r\n</resources>"
  },
  {
    "path": "push_demo/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"app_name\">push_demo</string>\n    <string name=\"hello_world\">Hello world!</string>\n    <string name=\"action_settings\">Settings</string>\n    <string name=\"login_account\">发送用户id</string>\n    <string name=\"login_target_account\">接收用户id</string>\n    <string name=\"login_login\">登  录</string>\n    <string name=\"main_settings\">设置</string>\n    <string name=\"main_contacts\">通讯录</string>\n    <string name=\"setting_notification\">接收新消息通知</string>\n    <string name=\"setting_sound\">声音</string>\n    <string name=\"setting_vibrate\">震动</string>\n    <string name=\"setting_logout\">注销</string>\n    <string name=\"chat_send\">发 送</string>\n    <string name=\"text_ack_msg\">已读</string>\n    <string name=\"text_delivered_msg\">送达</string>\n    <string name=\"text_failure\">失败</string>\n    <string name=\"text_sended_msg\">已发送</string>\n    <string name=\"chat_activity_header\">%1$d -> %2$d</string>\n\n</resources>\n"
  },
  {
    "path": "push_demo/res/values/styles.xml",
    "content": "<resources xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!--login-->\n    <style name=\"login_edittext_underline\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">1dp</item>\n        <item name=\"android:background\">@color/white_trans_40</item>\n        <item name=\"android:layout_marginLeft\">15dp</item>\n        <item name=\"android:layout_marginRight\">15dp</item>\n    </style>\n\n    <style name=\"login_edittext\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">45dp</item>\n        <item name=\"android:background\">@null</item>\n        <item name=\"android:layout_marginLeft\">20dp</item>\n        <item name=\"android:layout_marginRight\">15dp</item>\n        <item name=\"android:textColorHint\">@color/white_trans_40</item>\n        <item name=\"android:textColor\">@android:color/white</item>\n        <item name=\"android:textSize\">@dimen/h2</item>\n        <item name=\"android:numeric\">integer</item>\n        <item name=\"android:singleLine\">true</item>\n\n    </style>\n\n\n\n    <style name=\"AnimFade2\" parent=\"@android:style/Animation.Activity\">\n        <item name=\"android:activityOpenEnterAnimation\">@anim/slide_in_from_right</item>\n        <item name=\"android:activityOpenExitAnimation\">@anim/slide_out_to_left</item>\n        <item name=\"android:activityCloseExitAnimation\">@anim/slide_out_to_right</item>\n        <item name=\"android:activityCloseEnterAnimation\">@anim/slide_in_from_left</item>\n    </style>\n\n\n    <style name=\"Horizontal_Slide\" parent=\"android:Theme.NoTitleBar\">\n        <item name=\"android:windowAnimationStyle\">@style/AnimFade2</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "push_demo/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "push_demo/res/values-zh-rCN/hwpush_strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\t<string name=\"cloudpush_app_name\">\"推送服务\"</string>\n    <string name=\"hwpush_goback\">\"后退\"</string>\n    <string name=\"hwpush_forward\">\"前进\"</string>\n    <string name=\"hwpush_refresh\">\"刷新\"</string>\n    <string name=\"hwpush_collect\">\"收藏\"</string>\n    <string name=\"hwpush_deltitle\">\"删除通知\"</string>\n    <string name=\"hwpush_msg_collect\">\"通知收藏\"</string>\n    <string name=\"hwpush_msg_favorites\">通知收藏</string>\n    \n    <string name=\"hwpush_delete\">\"删除\"</string>\n    <string name=\"hwpush_selectall\">\"全选\"</string>\n    \n    <string name=\"hwpush_collect_tip\">\"点击收藏后，将在桌面自动生成一个“通知收藏”的图标，以后可从桌面直接点击查看。\"</string>\n    <string name=\"hwpush_collect_tip_known\">\"知道了\"</string>\n\n\t<string name=\"hwpush_cancel\">\"取消\"</string>\n    <plurals name=\"hwpush_delete_tip\">\n        <!--  The example of %d is 12-->\n        <item quantity=\"other\">\"是否删除所选通知？\"</item>\n    </plurals>\n\t<string name=\"hwpush_unselectall\">\"取消全选\"</string>\n\t<string name=\"hwpush_no_collection\">\"没有通知收藏\"</string>\n\t<string name=\"hwpush_richmedia\">\"富媒体\"</string>\n\t<string name=\"hwpush_loading_title\">\"正在加载，请稍候...\"</string>\n</resources>\n"
  },
  {
    "path": "push_demo/src/io/gobelieve/im/demo/BaseActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.os.Bundle;\nimport androidx.fragment.app.FragmentActivity;\n\nimport com.beetle.im.IMService;\n\nimport java.util.List;\n\n/**\n * BaseActivity\n * Description: 基础Activity\n */\npublic abstract class BaseActivity extends FragmentActivity {\n    @Override\n    protected final void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        onBaseCreate(savedInstanceState);\n        initView(savedInstanceState);\n    }\n\n    /**\n     * 必须在此设置一个ContentView，除非它没有界面\n     *\n     * @param savedInstanceState\n     */\n    protected abstract void onBaseCreate(Bundle savedInstanceState);\n\n    /**\n     * 视图初始化\n     * <p/>\n     * 处理手势绑定、view和fragment的注入\n     *\n     * @param savedInstanceState\n     */\n    protected abstract void initView(Bundle savedInstanceState);\n\n\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n\n        if (!isAppOnForeground()) {\n            //app 进入后台,停止IMService,采用push机制接收离线消息\n            IMService.getInstance().enterBackground();\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        IMService.getInstance().enterForeground();\n    }\n\n    /**\n     * 程序是否在前台运行\n     *\n     * @return\n     */\n    public boolean isAppOnForeground() {\n        // Returns a list of application processes that are running on the\n        // device\n\n        ActivityManager activityManager =\n            (ActivityManager) getApplicationContext().getSystemService(\n                Context.ACTIVITY_SERVICE);\n        String packageName = getApplicationContext().getPackageName();\n\n        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager\n            .getRunningAppProcesses();\n        if (appProcesses == null)\n            return false;\n\n        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {\n            // The name of the process that this object is associated with.\n            if (appProcess.processName.equals(packageName)\n                && appProcess.importance\n                == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "push_demo/src/io/gobelieve/im/demo/HuaweiPushReceiver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\r\n\r\nimport android.content.Context;\r\nimport android.os.Bundle;\r\nimport android.text.TextUtils;\r\nimport android.util.Log;\r\n\r\nimport com.huawei.android.pushagent.api.PushEventReceiver;\r\n\r\n/*\r\n * 接收Push所有消息的广播接收器\r\n */\r\npublic class HuaweiPushReceiver extends PushEventReceiver {\r\n\r\n    private static final String TAG = \"HuaweiPush\";\r\n    /*\r\n     * 显示Push消息\r\n     */\r\n    public void showPushMessage(int type, String msg) {\r\n//        PustDemoActivity mPustTestActivity = MyApplication.instance().getMainActivity();\r\n//        if (mPustTestActivity != null) {\r\n//            Handler handler = mPustTestActivity.getHandler();\r\n//            if (handler != null) {\r\n//                Message message = handler.obtainMessage();\r\n//                message.what = type;\r\n//                message.obj = msg;\r\n//                handler.sendMessageDelayed(message, 1L);\r\n//            }\r\n//        }\r\n    }\r\n\r\n    @Override\r\n    public void onToken(Context context, String token, Bundle extras) {\r\n        String belongId = extras.getString(\"belongId\");\r\n        String content = \"获取huawei push token和belongId成功，token = \" + token + \",belongId = \" + belongId;\r\n        Log.d(TAG, content);\r\n        if (!TextUtils.isEmpty(token)) {\r\n            PushDemoApplication.getApplication().setHuaweiPushToken(token);\r\n//        showPushMessage(PustDemoActivity.RECEIVE_TOKEN_MSG, content);\r\n        }\r\n    }\r\n\r\n\r\n    @Override\r\n    public boolean onPushMsg(Context context, byte[] msg, Bundle bundle) {\r\n        try {\r\n            String content = new String(msg, \"UTF-8\");\r\n            Log.d(TAG, \"收到一条huaweiPush消息： \" + content);\r\n//            showPushMessage(PustDemoActivity.RECEIVE_PUSH_MSG, content);\r\n        } catch (Exception e) {\r\n            e.printStackTrace();\r\n        }\r\n        return false;\r\n    }\r\n\r\n    public void onEvent(Context context, Event event, Bundle extras) {\r\n//        if (Event.NOTIFICATION_OPENED.equals(event) || Event.NOTIFICATION_CLICK_BTN.equals(event)) {\r\n//            int notifyId = extras.getInt(BOUND_KEY.pushNotifyId, 0);\r\n//            if (0 != notifyId) {\r\n//                NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);\r\n//                manager.cancel(notifyId);\r\n//            }\r\n//            String content = \"收到通知附加消息： \" + extras.getString(BOUND_KEY.pushMsgKey);\r\n//            Log.d(PustDemoActivity.TAG, content);\r\n//            showPushMessage(PustDemoActivity.RECEIVE_NOTIFY_CLICK_MSG, content);\r\n//        } else if (Event.PLUGINRSP.equals(event)) {\r\n//            final int TYPE_LBS = 1;\r\n//            final int TYPE_TAG = 2;\r\n//            int reportType = extras.getInt(BOUND_KEY.PLUGINREPORTTYPE, -1);\r\n//            boolean isSuccess = extras.getBoolean(BOUND_KEY.PLUGINREPORTRESULT, false);\r\n//            String message = \"\";\r\n//            if (TYPE_LBS == reportType) {\r\n//                message = \"LBS report result :\";\r\n//            } else if(TYPE_TAG == reportType) {\r\n//                message = \"TAG report result :\";\r\n//            }\r\n//            Log.d(PustDemoActivity.TAG, message + isSuccess);\r\n//            showPushMessage(PustDemoActivity.RECEIVE_TAG_LBS_MSG, message + isSuccess);\r\n//        }\r\n        super.onEvent(context, event, extras);\r\n    }\r\n}\r\n"
  },
  {
    "path": "push_demo/src/io/gobelieve/im/demo/LoginActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.content.Intent;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.Toast;\n\nimport com.beetle.bauhinia.PeerMessageActivity;\nimport com.beetle.bauhinia.api.IMHttpAPI;\nimport com.beetle.bauhinia.db.PeerMessageHandler;\nimport com.beetle.im.IMService;\n\nimport org.apache.http.Header;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.HttpStatus;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.entity.StringEntity;\nimport org.apache.http.impl.client.DefaultHttpClient;\nimport org.apache.http.message.BasicHeader;\nimport org.apache.http.protocol.HTTP;\nimport org.json.JSONObject;\n\nimport java.io.InputStream;\n\n/**\n * LoginActivity\n * Description: 登录页面,给用户指定消息发送方Id\n */\npublic class LoginActivity extends BaseActivity implements View.OnClickListener {\n    private EditText mEtAccount;\n    private EditText mEtTargetAccount;\n\n    AsyncTask mLoginTask;\n    @Override\n    protected void onBaseCreate(Bundle savedInstanceState) {\n        setContentView(R.layout.activity_login);\n    }\n\n    @Override\n    protected void initView(Bundle savedInstanceState) {\n        Button btnLogin = (Button) findViewById(R.id.btn_login);\n        mEtAccount = (EditText) findViewById(R.id.et_username);\n        mEtTargetAccount = (EditText) findViewById(R.id.et_target_username);\n        btnLogin.setOnClickListener(this);\n    }\n\n    //登录成功绑定推送的devicetoken\n    private void go2Chat(long sender, long receiver, String token) {\n        PushDemoApplication.getApplication().bindDeviceTokenToIM();\n    }\n\n    @Override\n    public void onClick(View v) {\n        if (v.getId() == R.id.btn_login) {\n            if (mEtAccount.getText().toString().length() <= 0) {\n                Toast.makeText(this, \"请设置您的用户id\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n            final long senderId = Long.parseLong(mEtAccount.getText().toString());\n            if (senderId <= 0) {\n                Toast.makeText(this, \"用户id不能为0或者-1\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            if (mEtTargetAccount.getText().toString().length() <= 0) {\n                Toast.makeText(this, \"请设置接收者的用户id\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n            final long receiverId = Long.parseLong(mEtTargetAccount.getText().toString());\n            if (receiverId <= 0) {\n                Toast.makeText(this, \"接收方id不能为0或者-1\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n\n            if (mLoginTask != null) {\n                return;\n            }\n\n            mLoginTask = new AsyncTask<Void, Integer, String>() {\n                @Override\n                protected String doInBackground(Void... urls) {\n                    return LoginActivity.this.login(senderId);\n                }\n                @Override\n                protected void onPostExecute(String result) {\n\n\n\n                    mLoginTask = null;\n                    if (result != null && result.length() > 0) {\n                        //设置用户id,进入MainActivity\n                        go2Chat(senderId, receiverId, result);\n                    } else {\n                        Toast.makeText(LoginActivity.this, \"登陆失败\", Toast.LENGTH_SHORT).show();\n                    }\n                }\n            }.execute();\n\n        }\n    }\n\n    private String login(long uid) {\n        //调用app自身的登陆接口获取im服务必须的access token,之后可将token保存在本地供下次直接登录IM服务\n        //sandbox地址: \"http://sandbox.demo.gobelieve.io\"\n        String URL = \"http://demo.gobelieve.io\";\n\n        String uri = String.format(\"%s/auth/token\", URL);\n        try {\n            HttpClient getClient = new DefaultHttpClient();\n            HttpPost request = new HttpPost(uri);\n            JSONObject json = new JSONObject();\n            json.put(\"uid\", uid);\n            StringEntity s = new StringEntity(json.toString());\n            s.setContentEncoding((Header) new BasicHeader(HTTP.CONTENT_TYPE, \"application/json\"));\n            request.setEntity(s);\n\n            HttpResponse response = getClient.execute(request);\n            int statusCode = response.getStatusLine().getStatusCode();\n            if (statusCode != HttpStatus.SC_OK){\n                System.out.println(\"login failure code is:\"+statusCode);\n                return null;\n            }\n            int len = (int)response.getEntity().getContentLength();\n            byte[] buf = new byte[len];\n            InputStream inStream = response.getEntity().getContent();\n            int pos = 0;\n            while (pos < len) {\n                int n = inStream.read(buf, pos, len - pos);\n                if (n == -1) {\n                    break;\n                }\n                pos += n;\n            }\n            inStream.close();\n            if (pos != len) {\n                return null;\n            }\n            String txt = new String(buf, \"UTF-8\");\n            JSONObject jsonObject = new JSONObject(txt);\n            String accessToken = jsonObject.getString(\"token\");\n            return accessToken;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    private static final char HEX_DIGITS[] = {\n            '0',\n            '1',\n            '2',\n            '3',\n            '4',\n            '5',\n            '6',\n            '7',\n            '8',\n            '9',\n            'A',\n            'B',\n            'C',\n            'D',\n            'E',\n            'F'\n    };\n\n    public final static String bin2Hex(byte[] b) {\n        StringBuilder sb = new StringBuilder(b.length * 2);\n        for (int i = 0; i < b.length; i++) {\n            sb.append(HEX_DIGITS[(b[i] & 0xf0) >>> 4]);\n            sb.append(HEX_DIGITS[b[i] & 0x0f]);\n        }\n        return sb.toString();\n    }\n\n\n}\n"
  },
  {
    "path": "push_demo/src/io/gobelieve/im/demo/PushDemoApplication.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.app.Application;\nimport android.os.Build;\nimport android.provider.Settings;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.beetle.bauhinia.api.IMHttpAPI;\nimport com.beetle.bauhinia.api.body.PostDeviceToken;\nimport com.beetle.bauhinia.db.GroupMessageDB;\nimport com.beetle.bauhinia.db.GroupMessageHandler;\nimport com.beetle.bauhinia.db.PeerMessageDB;\nimport com.beetle.bauhinia.db.PeerMessageHandler;\nimport com.beetle.bauhinia.tools.FileCache;\nimport com.beetle.im.IMService;\nimport com.huawei.android.pushagent.api.PushManager;\nimport com.tencent.android.tpush.XGIOperateCallback;\nimport com.tencent.android.tpush.XGPushManager;\nimport com.xiaomi.mipush.sdk.MiPushClient;\n\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.functions.Action1;\n\n\n/**\n * PushDemoApplication\n * Description:\n */\npublic class PushDemoApplication extends Application {\n    private static PushDemoApplication sApplication;\n\n    private String mXGPushToken = null;\n    private String mXiaomiPushToken = null;\n    private String mHuaweiPushToken = null;\n    private boolean mIsLogin = false;\n    private boolean mIsBind = false;\n\n    private final int USE_XG = 1;\n    private final int USE_XM = 0;\n    private final int USE_HW = 0;\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        sApplication = this;\n\n        IMService mIMService = IMService.getInstance();\n        //app可以单独部署服务器，给予第三方应用更多的灵活性\n        //sandbox地址:\"sandbox.imnode.gobelieve.io\", \"sandbox.pushnode.gobelieve.io\"\n        //\"http://sandbox.api.gobelieve.io\",\n        mIMService.setHost(\"imnode.gobelieve.io\");\n        IMHttpAPI.setAPIURL(\"http://api.gobelieve.io\");\n        initPush();\n    }\n\n    private void initPush() {\n        if (USE_HW != 0) {\n            initHuaweiPush();\n        } else if (USE_XM != 0) {\n            initXiaomiPush();\n        } else if (USE_XG != 0) {\n            initXGPush();\n        }\n    }\n\n    public static PushDemoApplication getApplication() {\n        return sApplication;\n    }\n\n    private boolean isXiaomiDevice() {\n        String os = Build.HOST;\n        return !TextUtils.isEmpty(os) && os.toLowerCase().contains(\"miui\");\n    }\n\n    private void initXGPush() {\n        XGIOperateCallback callback = new XGIOperateCallback() {\n            @Override\n            public void onSuccess(Object data, int i) {\n                Log.d(\"TPush\", \"注册成功，设备token为：\" + data);\n                PushDemoApplication.this.setXGPushToken((String)data);\n            }\n\n            @Override\n            public void onFail(Object data, int errCode, String msg) {\n                Log.d(\"TPush\", \"注册失败，错误码：\" + errCode + \",错误信息：\" + msg);\n            }\n\n        };\n        //接入信鸽推送\n        XGPushManager.registerPush(getApplicationContext(), callback);\n    }\n    private void initXiaomiPush() {\n        // 注册push服务，注册成功后会向XiaomiPushReceiver发送广播\n        // 可以从onCommandResult方法中MiPushCommandMessage对象参数中获取注册信息\n        String appId = \"2882303761517422920\";\n        String appKey = \"5111742288920\";\n        MiPushClient.registerPush(this, appId, appKey);\n    }\n\n    public void setXiaomiPushToken(String token) {\n        this.mXiaomiPushToken = token;\n        if (!TextUtils.isEmpty(mXiaomiPushToken) && mIsLogin && !mIsBind) {\n            // 已登录尚未绑定时\n            bindWithXiaomi();\n        }\n    }\n\n    private boolean isHuaweiDevice() {\n        String os = Build.HOST;\n        return !TextUtils.isEmpty(os) && os.toLowerCase().contains(\"huawei\");\n    }\n\n    private void initHuaweiPush() {\n        PushManager.requestToken(this);\n    }\n\n    public void setHuaweiPushToken(String token) {\n        this.mHuaweiPushToken = token;\n        if (!TextUtils.isEmpty(mHuaweiPushToken) && mIsLogin && !mIsBind) {\n            // 已登录尚未绑定时\n            bindWithHuawei();\n        }\n    }\n\n    public void setXGPushToken(String token) {\n        this.mXGPushToken = token;\n        if (!TextUtils.isEmpty(mXGPushToken) && mIsLogin && !mIsBind) {\n            // 已登录尚未绑定时\n            bindWithXG();\n        }\n    }\n\n    public void bindDeviceTokenToIM() {\n        mIsLogin = true;\n\n        if (!TextUtils.isEmpty(mHuaweiPushToken)) {\n            bindWithHuawei();\n        }\n        // 小米情况同华为\n        if (!TextUtils.isEmpty(mXiaomiPushToken)) {\n            bindWithXiaomi();\n        }\n        if (!TextUtils.isEmpty(mXGPushToken)) {\n            bindWithXG();\n        }\n    }\n\n    private void bindWithHuawei() {\n        PostDeviceToken postDeviceToken = new PostDeviceToken();\n        postDeviceToken.hwDeviceToken = mHuaweiPushToken;\n        bindDeviceTokenToIM(postDeviceToken);\n    }\n\n    private void bindWithXiaomi() {\n        PostDeviceToken postDeviceToken = new PostDeviceToken();\n        postDeviceToken.xmDeviceToken = mXiaomiPushToken;\n        bindDeviceTokenToIM(postDeviceToken);\n    }\n\n    private void bindWithXG() {\n        PostDeviceToken postDeviceToken = new PostDeviceToken();\n        postDeviceToken.xgDeviceToken = mXGPushToken;\n        bindDeviceTokenToIM(postDeviceToken);\n    }\n\n    private void bindDeviceTokenToIM(PostDeviceToken postDeviceToken) {\n        IMHttpAPI.Singleton().bindDeviceToken(postDeviceToken)\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Action1<Object>() {\n                    @Override\n                    public void call(Object obj) {\n                        Log.i(\"im\", \"bind success\");\n                    }\n                }, new Action1<Throwable>() {\n                    @Override\n                    public void call(Throwable throwable) {\n                        Log.i(\"im\", \"bind fail\");\n                    }\n                });\n    }\n}\n"
  },
  {
    "path": "push_demo/src/io/gobelieve/im/demo/XGMessageReceiver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\r\n\r\nimport org.json.JSONException;\r\nimport org.json.JSONObject;\r\n\r\nimport android.content.Context;\r\nimport android.content.Intent;\r\nimport android.util.Log;\r\nimport android.widget.Toast;\r\n\r\nimport com.tencent.android.tpush.XGPushBaseReceiver;\r\nimport com.tencent.android.tpush.XGPushClickedResult;\r\nimport com.tencent.android.tpush.XGPushRegisterResult;\r\nimport com.tencent.android.tpush.XGPushShowedResult;\r\nimport com.tencent.android.tpush.XGPushTextMessage;\r\n\r\npublic class XGMessageReceiver extends XGPushBaseReceiver {\r\n\tprivate Intent intent = new Intent(\"com.qq.xgdemo.activity.UPDATE_LISTVIEW\");\r\n\tpublic static final String LogTag = \"TPushReceiver\";\r\n\r\n\tprivate void show(Context context, String text) {\r\n\t\tLog.i(LogTag,  text);\r\n\t\tToast.makeText(context, text, Toast.LENGTH_SHORT).show();\r\n\t}\r\n\r\n\t// 通知展示\r\n\t@Override\r\n\tpublic void onNotifactionShowedResult(Context context,\r\n\t\t\tXGPushShowedResult notifiShowedRlt) {\r\n\t\tif (context == null || notifiShowedRlt == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tshow(context, \"您有1条新消息, \" + \"通知被展示 ， \" + notifiShowedRlt.toString());\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void onUnregisterResult(Context context, int errorCode) {\r\n\t\tif (context == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tString text = \"\";\r\n\t\tif (errorCode == XGPushBaseReceiver.SUCCESS) {\r\n\t\t\ttext = \"反注册成功\";\r\n\t\t} else {\r\n\t\t\ttext = \"反注册失败\" + errorCode;\r\n\t\t}\r\n\t\tLog.d(LogTag, text);\r\n\t\tshow(context, text);\r\n\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void onSetTagResult(Context context, int errorCode, String tagName) {\r\n\t\tif (context == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tString text = \"\";\r\n\t\tif (errorCode == XGPushBaseReceiver.SUCCESS) {\r\n\t\t\ttext = \"\\\"\" + tagName + \"\\\"设置成功\";\r\n\t\t} else {\r\n\t\t\ttext = \"\\\"\" + tagName + \"\\\"设置失败,错误码：\" + errorCode;\r\n\t\t}\r\n\t\tLog.d(LogTag, text);\r\n\t\tshow(context, text);\r\n\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void onDeleteTagResult(Context context, int errorCode, String tagName) {\r\n\t\tif (context == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tString text = \"\";\r\n\t\tif (errorCode == XGPushBaseReceiver.SUCCESS) {\r\n\t\t\ttext = \"\\\"\" + tagName + \"\\\"删除成功\";\r\n\t\t} else {\r\n\t\t\ttext = \"\\\"\" + tagName + \"\\\"删除失败,错误码：\" + errorCode;\r\n\t\t}\r\n\t\tLog.d(LogTag, text);\r\n\t\tshow(context, text);\r\n\r\n\t}\r\n\r\n\t// 通知点击回调 actionType=1为该消息被清除，actionType=0为该消息被点击\r\n\t@Override\r\n\tpublic void onNotifactionClickedResult(Context context,\r\n\t\t\tXGPushClickedResult message) {\r\n\t\tif (context == null || message == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tString text = \"\";\r\n\t\tif (message.getActionType() == XGPushClickedResult.NOTIFACTION_CLICKED_TYPE) {\r\n\t\t\t// 通知在通知栏被点击啦。。。。。\r\n\t\t\t// APP自己处理点击的相关动作\r\n\t\t\t// 这个动作可以在activity的onResume也能监听，请看第3点相关内容\r\n\t\t\ttext = \"通知被打开 :\" + message;\r\n\t\t} else if (message.getActionType() == XGPushClickedResult.NOTIFACTION_DELETED_TYPE) {\r\n\t\t\t// 通知被清除啦。。。。\r\n\t\t\t// APP自己处理通知被清除后的相关动作\r\n\t\t\ttext = \"通知被清除 :\" + message;\r\n\t\t}\r\n\t\tToast.makeText(context, \"广播接收到通知被点击:\" + message.toString(),\r\n\t\t\t\tToast.LENGTH_SHORT).show();\r\n\t\t// 获取自定义key-value\r\n\t\tString customContent = message.getCustomContent();\r\n\t\tif (customContent != null && customContent.length() != 0) {\r\n\t\t\ttry {\r\n\t\t\t\tJSONObject obj = new JSONObject(customContent);\r\n\t\t\t\t// key1为前台配置的key\r\n\t\t\t\tif (!obj.isNull(\"key\")) {\r\n\t\t\t\t\tString value = obj.getString(\"key\");\r\n\t\t\t\t\tLog.d(LogTag, \"get custom value:\" + value);\r\n\t\t\t\t}\r\n                Log.i(LogTag, \"customer content:\" + obj);\r\n\t\t\t} catch (JSONException e) {\r\n\t\t\t\te.printStackTrace();\r\n\t\t\t}\r\n\t\t}\r\n\t\t// APP自主处理的过程。。。\r\n\t\tLog.d(LogTag, text);\r\n\t\tshow(context, text);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void onRegisterResult(Context context, int errorCode,\r\n\t\t\tXGPushRegisterResult message) {\r\n\t\t// TODO Auto-generated method stub\r\n\t\tif (context == null || message == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tString text = \"\";\r\n\t\tif (errorCode == XGPushBaseReceiver.SUCCESS) {\r\n\t\t\ttext = message + \"注册成功\";\r\n\t\t\t// 在这里拿token\r\n\t\t\tString token = message.getToken();\r\n\t\t} else {\r\n\t\t\ttext = message + \"注册失败，错误码：\" + errorCode;\r\n\t\t}\r\n\t\tLog.d(LogTag, text);\r\n\t\tshow(context, text);\r\n\t}\r\n\r\n\t// 消息透传\r\n\t@Override\r\n\tpublic void onTextMessage(Context context, XGPushTextMessage message) {\r\n\t\t// TODO Auto-generated method stub\r\n\t\tString text = \"收到消息:\" + message.toString();\r\n\t\t// 获取自定义key-value\r\n\t\tString customContent = message.getCustomContent();\r\n\t\tif (customContent != null && customContent.length() != 0) {\r\n\t\t\ttry {\r\n\t\t\t\tJSONObject obj = new JSONObject(customContent);\r\n\t\t\t\t// key1为前台配置的key\r\n\t\t\t\tif (!obj.isNull(\"key\")) {\r\n\t\t\t\t\tString value = obj.getString(\"key\");\r\n\t\t\t\t\tLog.d(LogTag, \"get custom value:\" + value);\r\n\t\t\t\t}\r\n\t\t\t\t// ...\r\n\t\t\t} catch (JSONException e) {\r\n\t\t\t\te.printStackTrace();\r\n\t\t\t}\r\n\t\t}\r\n\t\t// APP自主处理消息的过程...\r\n\t\tLog.d(LogTag, text);\r\n\t\tshow(context, text);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "push_demo/src/io/gobelieve/im/demo/XiaomiPushReceiver.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.xiaomi.mipush.sdk.ErrorCode;\nimport com.xiaomi.mipush.sdk.MiPushClient;\nimport com.xiaomi.mipush.sdk.MiPushCommandMessage;\nimport com.xiaomi.mipush.sdk.MiPushMessage;\nimport com.xiaomi.mipush.sdk.PushMessageReceiver;\n\n\n/**\n * 1、PushMessageReceiver是个抽象类，该类继承了BroadcastReceiver。\n * 2、需要将自定义的DemoMessageReceiver注册在AndroidManifest.xml文件中 <receiver\n * android:exported=\"true\"\n * android:name=\"com.xiaomi.mipushdemo.DemoMessageReceiver\"> <intent-filter>\n * <action android:name=\"com.xiaomi.mipush.RECEIVE_MESSAGE\" /> </intent-filter>\n * <intent-filter> <action android:name=\"com.xiaomi.mipush.ERROR\" />\n * </intent-filter> <intent-filter>\n * <action android:name=\"com.xiaomi.mipush.MESSAGE_ARRIVED\" /></intent-filter>\n * </receiver>\n * 3、DemoMessageReceiver的onReceivePassThroughMessage方法用来接收服务器向客户端发送的透传消息\n * 4、DemoMessageReceiver的onNotificationMessageClicked方法用来接收服务器向客户端发送的通知消息，\n * 这个回调方法会在用户手动点击通知后触发\n * 5、DemoMessageReceiver的onNotificationMessageArrived方法用来接收服务器向客户端发送的通知消息，\n * 这个回调方法是在通知消息到达客户端时触发。另外应用在前台时不弹出通知的通知消息到达客户端也会触发这个回调函数\n * 6、DemoMessageReceiver的onCommandResult方法用来接收客户端向服务器发送命令后的响应结果\n * 7、DemoMessageReceiver的onReceiveRegisterResult方法用来接收客户端向服务器发送注册命令后的响应结果\n * 8、以上这些方法运行在非UI线程中\n * <p>\n * 9、在miui中，如果程序未启动，且不在自启动白名单中，onReceivePassThroughMessage和onNotificationMessageArrived\n * 将不会被触发，其余方法则可以，并启动application\n * <p>\n * Created by ZhangWF(zhangwf0929@gmail.com) on 15/7/8.\n */\npublic class XiaomiPushReceiver extends PushMessageReceiver  {\n\n    private static final String TAG = \"XiaomiPush\";\n\n    /**\n     * 接收服务器推送的透传消息\n     *\n     * @param context\n     * @param message\n     */\n    @Override\n    public void onReceivePassThroughMessage(Context context, MiPushMessage message) {\n        Log.d(TAG, \"onReceivePassThroughMessage \" + message);\n    }\n\n    /**\n     * 接收服务器推送的通知消息，用户点击后触发\n     * <p>\n     * 如果用户点击了预定义通知消息，消息不会通过onNotificationMessageClicked方法传到客户端。\n     * 如果服务端调用Message.Builder类的extra(String key, String value)方法设置了Constants.EXTRA_PARAM_NOTIFY_EFFECT的值，则为预定义通知消息；否则为自定义通知消息\n     *\n     * @param context\n     * @param message\n     */\n    @Override\n    public void onNotificationMessageClicked(Context context, MiPushMessage message) {\n        Log.d(TAG, \"onNotificationMessageClicked \" + message);\n    }\n\n    /**\n     * 接收服务器推送的通知消息，消息到达客户端时触发\n     * <p>\n     * 在MIUI上，只有应用处于启动状态，或者自启动白名单中，才可以通过此方法接受到该消息。\n     *\n     * @param context\n     * @param message\n     */\n    @Override\n    public void onNotificationMessageArrived(Context context, MiPushMessage message) {\n        Log.d(TAG, \"onNotificationMessageArrived \" + message);\n    }\n\n    /**\n     * 获取给服务器发送命令的结果\n     *\n     * @param context\n     * @param message\n     */\n    @Override\n    public void onCommandResult(Context context, MiPushCommandMessage message) {\n        Log.d(TAG, \"onCommandResult \" + message);\n        if (message == null || TextUtils.isEmpty(message.getCommand()) || message.getResultCode() != ErrorCode.SUCCESS) {\n            return;\n        }\n        String command = message.getCommand();\n//        List<String> arguments = message.getCommandArguments();\n//        String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);\n//        String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null);\n//        String reason = message.getReason();;\n\n        switch (command) {\n            // 注册\n            case MiPushClient.COMMAND_REGISTER:\n                break;\n            // 别名\n            case MiPushClient.COMMAND_SET_ALIAS:\n                break;\n            // 取消别名\n            case MiPushClient.COMMAND_UNSET_ALIAS:\n                break;\n            // 设置账号\n            case MiPushClient.COMMAND_SET_ACCOUNT:\n                break;\n            // 取消账号\n            case MiPushClient.COMMAND_UNSET_ACCOUNT:\n                break;\n            // 订阅标签\n            case MiPushClient.COMMAND_SUBSCRIBE_TOPIC:\n                break;\n            // 取消订阅标签\n            case MiPushClient.COMMAND_UNSUBSCRIBE_TOPIC:\n                break;\n            // 设置消息注册时间\n            case MiPushClient.COMMAND_SET_ACCEPT_TIME:\n                break;\n            default:\n                break;\n        }\n    }\n\n    /**\n     * 获取给服务器发送注册命令的结果\n     *\n     * @param context\n     * @param message\n     */\n    @Override\n    public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) {\n        Log.d(TAG, \"onReceiveRegisterResult \" + message);\n        if (message!=null && message.getCommandArguments()!=null && message.getCommandArguments().size()>0) {\n            PushDemoApplication.getApplication().setXiaomiPushToken(message.getCommandArguments().get(0));\n        }\n    }\n}\n"
  },
  {
    "path": "room_demo/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "room_demo/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"io.gobelieve.im.demo\">\n\n    <!-- 高德地图所需权限 -->\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n\n    <application\n        android:name=\".IMDemoApplication\"\n        android:allowBackup=\"true\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\">\n        <activity\n            android:name=\".LoginActivity\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@style/Horizontal_Slide\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <!-- 高德地图配置 -->\n        <meta-data\n            android:name=\"com.amap.api.v2.apikey\"\n            android:value=\"fe4ad96eb93239914540892d3dfb76f7\" />\n\n        <!-- 必需： 应用ID -->\n        <meta-data\n            android:name=\"GOBELIEVE_APPID\"\n            android:value=\"7\" />\n\n        <!-- 必需： 应用KEY -->\n        <meta-data\n            android:name=\"GOBELIEVE_APPKEY\"\n            android:value=\"sVDIlIiDUm7tWPYWhi6kfNbrqui3ez44\" />\n\n        <activity\n            android:name=\"com.beetle.bauhinia.PeerMessageActivity\"\n            android:exported=\"true\"\n            android:theme=\"@style/imkit.NoActionBar\"\n            android:windowSoftInputMode=\"adjustResize\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\"com.beetle.bauhinia.activity.PhotoActivity\"\n            android:label=\"照片\"\n            android:theme=\"@style/imkit.ActionBar\"></activity>\n        <activity\n            android:name=\"com.beetle.bauhinia.MapActivity\"\n            android:label=\"位置\"\n            android:theme=\"@style/imkit.ActionBar\"></activity>\n        <activity\n            android:name=\"com.beetle.bauhinia.LocationPickerActivity\"\n            android:label=\"位置\"\n            android:theme=\"@style/imkit.ActionBar\"></activity>\n\n\n        <activity\n            android:name=\".RoomActivity\"\n            android:exported=\"true\"\n            android:theme=\"@style/imkit.ActionBar\"\n            android:windowSoftInputMode=\"adjustResize|stateHidden\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "room_demo/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 25\n    buildToolsVersion '28.0.3'\n    useLibrary  'org.apache.http.legacy'\n\n    defaultConfig {\n        minSdkVersion 18\n        targetSdkVersion 25\n        versionCode 1020\n        versionName \"1.0.2\"\n    }\n\n\n    sourceSets {\n        main {\n            manifest.srcFile 'AndroidManifest.xml'\n            java.srcDirs = ['src']\n            res.srcDirs = ['res']\n            aidl.srcDirs = ['src']\n            jniLibs.srcDirs = ['libs']\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n\n    implementation 'com.android.support:support-v4:25.0.0'\n    implementation 'com.android.support:appcompat-v7:25.0.0'\n    implementation project(':imkit')\n    implementation project(':imsdk')\n    implementation project(':asynctcp')\n}\n\nandroid {\n    packagingOptions {\n        exclude 'META-INF/DEPENDENCIES.txt'\n        exclude 'META-INF/LICENSE.txt'\n        exclude 'META-INF/NOTICE.txt'\n        exclude 'META-INF/NOTICE'\n        exclude 'META-INF/LICENSE'\n        exclude 'META-INF/DEPENDENCIES'\n        exclude 'META-INF/notice.txt'\n        exclude 'META-INF/license.txt'\n        exclude 'META-INF/dependencies.txt'\n        exclude 'META-INF/LGPL2.1'\n    }\n}\n\n\n\n"
  },
  {
    "path": "room_demo/res/anim/fade_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n    android:duration=\"300\" />"
  },
  {
    "path": "room_demo/res/anim/fade_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:fromAlpha=\"1.0\" android:toAlpha=\"0.0\"\n    android:duration=\"300\" />"
  },
  {
    "path": "room_demo/res/anim/head_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 左上角扩大-->\n  <scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"0.001\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"0.001\"   \n        android:toYScale=\"1.0\"   \n        android:pivotX=\"15%\"  \n        android:pivotY=\"25%\"  \n        android:duration=\"200\" />  \n   "
  },
  {
    "path": "room_demo/res/anim/head_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 左上角缩小 -->\n  <scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"0.001\"   \n        android:fromYScale=\"1.0\"   \n        android:toYScale=\"0.001\"   \n        android:pivotX=\"15%\"  \n        android:pivotY=\"25%\"  \n        android:duration=\"200\" />  \n   "
  },
  {
    "path": "room_demo/res/anim/hold.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?> \n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:interpolator=\"@android:anim/accelerate_interpolator\"\n       android:fromXDelta=\"0\" android:toXDelta=\"0\"\n       android:duration=\"300\" />"
  },
  {
    "path": "room_demo/res/anim/push_bottom_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"50%p\"\n        android:toYDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "room_demo/res/anim/push_bottom_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    \n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"50%p\" />\n\n    \n\n</set>"
  },
  {
    "path": "room_demo/res/anim/push_top_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"-50%p\"\n        android:toYDelta=\"0\" />   \n</set>"
  },
  {
    "path": "room_demo/res/anim/push_top_in2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"0\"   \n        android:toYScale=\"1.0\"   \n        android:pivotX=\"0\"  \n        android:pivotY=\"10%\"  \n        android:duration=\"200\" /> "
  },
  {
    "path": "room_demo/res/anim/push_top_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"-50%p\" />   \n</set>"
  },
  {
    "path": "room_demo/res/anim/push_top_out2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<scale   xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:interpolator=\"@android:anim/accelerate_decelerate_interpolator\"  \n        android:fromXScale=\"1.0\"   \n        android:toXScale=\"1.0\"   \n        android:fromYScale=\"1.0\"   \n        android:toYScale=\"0\"   \n        android:pivotX=\"0\"  \n        android:pivotY=\"10%\"  \n        android:duration=\"200\" /> "
  },
  {
    "path": "room_demo/res/anim/slide_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"-100%p\"\n        android:toXDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "room_demo/res/anim/slide_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"100%p\"\n        android:toXDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "room_demo/res/anim/slide_out_to_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"0\"\n        android:toXDelta=\"-100%p\" />\n\n</set>"
  },
  {
    "path": "room_demo/res/anim/slide_out_to_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <translate\n        android:duration=\"200\"\n        android:fromXDelta=\"0\"\n        android:toXDelta=\"100%p\" />\n\n</set>"
  },
  {
    "path": "room_demo/res/drawable/btn_login_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/btn_login_pressed\" android:state_pressed=\"true\" />\n    <item android:drawable=\"@drawable/btn_login_normal\" />\n\n\n</selector>\n"
  },
  {
    "path": "room_demo/res/layout/activity_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/bg_background\">\n\n    <!--发送用户id-->\n    <View\n        android:id=\"@+id/line_sender_underline\"\n        style=\"@style/login_edittext_underline\"\n        android:layout_marginTop=\"248dp\" />\n\n    <ImageView\n        android:id=\"@+id/iv_sender\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_above=\"@id/line_sender_underline\"\n        android:layout_alignBottom=\"@id/line_sender_underline\"\n        android:layout_marginBottom=\"9dp\"\n        android:layout_marginLeft=\"22dp\"\n        android:src=\"@drawable/ic_account\" />\n\n    <EditText\n        android:id=\"@+id/et_username\"\n        style=\"@style/login_edittext\"\n        android:layout_above=\"@id/line_sender_underline\"\n        android:layout_alignBottom=\"@id/line_sender_underline\"\n        android:layout_toRightOf=\"@id/iv_sender\"\n        android:hint=\"@string/login_account\" />\n\n    <!--接收用户id-->\n\n    <View\n        android:id=\"@+id/line_receiver_underline\"\n        style=\"@style/login_edittext_underline\"\n        android:layout_below=\"@id/line_sender_underline\"\n        android:layout_marginTop=\"44dp\" />\n\n\n    <ImageView\n        android:id=\"@+id/iv_receiver\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignBottom=\"@id/line_receiver_underline\"\n        android:layout_marginBottom=\"9dp\"\n        android:layout_marginLeft=\"22dp\"\n        android:src=\"@drawable/ic_account\" />\n\n    <EditText\n        android:id=\"@+id/et_target_username\"\n        style=\"@style/login_edittext\"\n        android:layout_alignBottom=\"@id/line_receiver_underline\"\n        android:layout_toRightOf=\"@id/iv_receiver\"\n        android:hint=\"@string/login_target_account\" />\n\n    <Button\n        android:id=\"@+id/btn_login\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/line_receiver_underline\"\n        android:layout_marginLeft=\"15dp\"\n        android:layout_marginRight=\"15dp\"\n        android:layout_marginTop=\"45dp\"\n        android:background=\"@drawable/btn_login_selector\"\n        android:text=\"@string/login_login\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"@dimen/h2\" />\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "room_demo/res/layout/activity_room.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    tools:context=\"io.gobelieve.im.demo.RoomActivity\">\n\n</RelativeLayout>\n"
  },
  {
    "path": "room_demo/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"common_bg\">#fcfcfc</color>\n    <color name=\"common_header_blue\">#00abf1</color>\n    <color name=\"view_focused\">#11000000</color>\n    <color name=\"view_pressed\">@color/view_focused</color>\n    <color name=\"btn_login_normal\">#2dafa3</color>\n    <color name=\"btn_login_pressed\">@color/common_header_blue</color>\n    <color name=\"btn_gray_normal\">#c0c0c0</color>\n    <color name=\"bottom_bar_normal_bg\">#2D2F31</color>\n    <color name=\"common_botton_bar_blue\">#2ea7e0</color>\n    <color name=\"common_bottom_bar_normal_bg\">#2d2f31</color>\n    <color name=\"common_bottom_bar_selected_bg\">#161718</color>\n    <color name=\"divider_list\">#cccccc</color>\n    <color name=\"blue_337aba\">#337aba</color>\n    <color name=\"setting_item_pressed\">@color/btn_login_pressed</color>\n    <color name=\"setting_divider\">#e6e6e6</color>\n    <color name=\"gray_33\">#333333</color>\n    <color name=\"bg_chat\">#f2f0eb</color>\n    <color name=\"gray_normal\">#666667</color>\n\n    <color name=\"white_trans_40\">#66FFFFFF</color>\n\n\n</resources>\n"
  },
  {
    "path": "room_demo/res/values/dimen_font.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- 一般不用h1和h6, header标题h2, catalog标题h3, tab名称h4, 图标文字h5 -->\n    <dimen name=\"large\">26sp</dimen>\n    <dimen name=\"h0\">24sp</dimen>\n    <dimen name=\"h1\">22sp</dimen>\n    <dimen name=\"h2\">20sp</dimen>\n    <dimen name=\"h3\">18sp</dimen>\n    <dimen name=\"h4\">16sp</dimen>\n    <dimen name=\"h5\">14sp</dimen>\n    <dimen name=\"h6\">12sp</dimen>\n    <dimen name=\"h7\">10sp</dimen>\n</resources>"
  },
  {
    "path": "room_demo/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n    <dimen name=\"header_common_height\">46dp</dimen>\n    <dimen name=\"header_button_width\">48dp</dimen>\n\n    <!--setting-->\n    <dimen name=\"setting_left_padding\">20dp</dimen>\n    <dimen name=\"setting_right_padding\">20dp</dimen>\n    <dimen name=\"setting_item_height\">47dp</dimen>\n    <dimen name=\"setting_item_left_padding\">27dp</dimen>\n    <dimen name=\"setting_item_right_padding\">27dp</dimen>\n    <dimen name=\"setting_item_checkbox_right_padding\">32dp</dimen>\n    <dimen name=\"setting_checkbox_right_margin\">14dp</dimen>\n\n\n    <dimen name=\"margin_chat_activity\">5dp</dimen>\n    <dimen name=\"size_avatar\">50dp</dimen>\n</resources>\n"
  },
  {
    "path": "room_demo/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"app_name\">RoomDemo</string>\n    <string name=\"action_settings\">Settings</string>\n    <string name=\"login_account\">用户id</string>\n    <string name=\"login_target_account\">房间id</string>\n    <string name=\"login_login\">登  录</string>\n    <string name=\"main_settings\">设置</string>\n    <string name=\"setting_notification\">接收新消息通知</string>\n    <string name=\"setting_sound\">声音</string>\n    <string name=\"setting_vibrate\">震动</string>\n    <string name=\"setting_logout\">注销</string>\n    <string name=\"chat_send\">发 送</string>\n    <string name=\"text_ack_msg\">已读</string>\n    <string name=\"text_failure\">失败</string>\n    <string name=\"chat_activity_header\">%1$d -> %2$d</string>\n\n</resources>\n"
  },
  {
    "path": "room_demo/res/values/styles.xml",
    "content": "<resources xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!--login-->\n    <style name=\"login_edittext_underline\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">1dp</item>\n        <item name=\"android:background\">@color/white_trans_40</item>\n        <item name=\"android:layout_marginLeft\">15dp</item>\n        <item name=\"android:layout_marginRight\">15dp</item>\n    </style>\n\n    <style name=\"login_edittext\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">45dp</item>\n        <item name=\"android:background\">@null</item>\n        <item name=\"android:layout_marginLeft\">20dp</item>\n        <item name=\"android:layout_marginRight\">15dp</item>\n        <item name=\"android:textColorHint\">@color/white_trans_40</item>\n        <item name=\"android:textColor\">@android:color/white</item>\n        <item name=\"android:textSize\">@dimen/h2</item>\n        <item name=\"android:numeric\">integer</item>\n        <item name=\"android:singleLine\">true</item>\n\n    </style>\n\n\n    <style name=\"AnimFade2\" parent=\"@android:style/Animation.Activity\">\n        <item name=\"android:activityOpenEnterAnimation\">@anim/slide_in_from_right</item>\n        <item name=\"android:activityOpenExitAnimation\">@anim/slide_out_to_left</item>\n        <item name=\"android:activityCloseExitAnimation\">@anim/slide_out_to_right</item>\n        <item name=\"android:activityCloseEnterAnimation\">@anim/slide_in_from_left</item>\n    </style>\n\n\n    <style name=\"Horizontal_Slide\" parent=\"android:Theme.NoTitleBar\">\n        <item name=\"android:windowAnimationStyle\">@style/AnimFade2</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "room_demo/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "room_demo/src/io/gobelieve/im/demo/IMDemoApplication.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.app.Application;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.provider.Settings;\nimport android.util.Log;\n\nimport com.beetle.bauhinia.api.IMHttpAPI;\nimport com.beetle.im.IMService;\n\n\n/**\n * IMDemoApplication\n * Description:\n */\npublic class IMDemoApplication extends Application {\n    private static final String TAG = \"gobelieve\";\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        IMService mIMService = IMService.getInstance();\n        //app可以单独部署服务器，给予第三方应用更多的灵活性\n        mIMService.setHost(\"imnode2.gobelieve.io\");\n        IMHttpAPI.setAPIURL(\"http://api.gobelieve.io\");\n\n        String androidID = Settings.Secure.getString(this.getContentResolver(),\n                Settings.Secure.ANDROID_ID);\n\n        //设置设备唯一标识,用于多点登录时设备校验\n        mIMService.setDeviceID(androidID);\n\n        //监听网路状态变更\n        IMService.getInstance().registerConnectivityChangeReceiver(getApplicationContext());\n    }\n}\n"
  },
  {
    "path": "room_demo/src/io/gobelieve/im/demo/LoginActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.app.ProgressDialog;\nimport android.content.Intent;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.appcompat.app.ActionBarActivity;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.Toast;\n\nimport com.beetle.bauhinia.api.IMHttpAPI;\nimport com.beetle.bauhinia.api.body.PostDeviceToken;\nimport com.beetle.bauhinia.db.GroupMessageHandler;\nimport com.beetle.bauhinia.db.PeerMessageHandler;\nimport com.beetle.im.IMService;\n\nimport org.apache.http.Header;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.HttpStatus;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.entity.StringEntity;\nimport org.apache.http.impl.client.DefaultHttpClient;\nimport org.apache.http.message.BasicHeader;\nimport org.apache.http.protocol.HTTP;\nimport org.json.JSONObject;\n\nimport java.io.InputStream;\n\n\n/**\n * LoginActivity\n * Description: 登录页面,给用户指定消息发送方Id\n */\npublic class LoginActivity extends FragmentActivity implements View.OnClickListener {\n    private EditText mEtAccount;\n    private EditText mEtTargetAccount;\n\n    AsyncTask mLoginTask;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_login);\n\n        Button btnLogin = (Button) findViewById(R.id.btn_login);\n        mEtAccount = (EditText) findViewById(R.id.et_username);\n        mEtTargetAccount = (EditText) findViewById(R.id.et_target_username);\n        btnLogin.setOnClickListener(this);\n\n    }\n\n\n    private void go2Chat(long sender, long receiver, String token) {\n        IMService.getInstance().stop();\n\n        PeerMessageHandler.getInstance().setUID(sender);\n        GroupMessageHandler.getInstance().setUID(sender);\n\n        IMHttpAPI.setToken(token);\n        IMService.getInstance().setToken(token);\n        IMService.getInstance().start();\n\n\n        Intent intent = new Intent(this, RoomActivity.class);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        intent.putExtra(\"room_id\", receiver);\n        intent.putExtra(\"room_name\", \"测试\");\n        intent.putExtra(\"current_uid\", sender);\n        startActivity(intent);\n        finish();\n    }\n\n\n\n    @Override\n    public void onClick(View v) {\n        if (v.getId() == R.id.btn_login) {\n            if (mEtAccount.getText().toString().length() <= 0) {\n                Toast.makeText(this, \"请设置您的用户id\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n            final long senderId = Long.parseLong(mEtAccount.getText().toString());\n            if (senderId <= 0) {\n                Toast.makeText(this, \"用户id不能为0或者-1\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            if (mEtTargetAccount.getText().toString().length() <= 0) {\n                Toast.makeText(this, \"请设置群组id\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n            final long receiverId = Long.parseLong(mEtTargetAccount.getText().toString());\n            if (receiverId <= 0) {\n                Toast.makeText(this, \"群组id不能为0或者-1\", Toast.LENGTH_SHORT).show();\n                return;\n            }\n\n            if (mLoginTask != null) {\n                return;\n            }\n            final ProgressDialog dialog = ProgressDialog.show(this, null, \"登录中...\");\n            mLoginTask = new AsyncTask<Void, Integer, String>() {\n                @Override\n                protected String doInBackground(Void... urls) {\n                    return LoginActivity.this.login(senderId);\n                }\n                @Override\n                protected void onPostExecute(String result) {\n                    dialog.dismiss();\n                    mLoginTask = null;\n                    if (result != null && result.length() > 0) {\n                        //设置用户id,进入MainActivity\n                        go2Chat(senderId, receiverId, result);\n                    } else {\n                        Toast.makeText(LoginActivity.this, \"登陆失败\", Toast.LENGTH_SHORT).show();\n                    }\n                }\n            }.execute();\n\n        }\n    }\n\n    private String login(long uid) {\n        //调用app自身的登陆接口获取im服务必须的access token,之后可将token保存在本地供下次直接登录IM服务\n        //sandbox地址: \"http://sandbox.demo.gobelieve.io\"\n        String URL = \"http://demo.gobelieve.io\";\n        String uri = String.format(\"%s/auth/token\", URL);\n        try {\n            HttpClient getClient = new DefaultHttpClient();\n            HttpPost request = new HttpPost(uri);\n            JSONObject json = new JSONObject();\n            json.put(\"uid\", uid);\n            StringEntity s = new StringEntity(json.toString());\n            s.setContentEncoding((Header) new BasicHeader(HTTP.CONTENT_TYPE, \"application/json\"));\n            request.setEntity(s);\n\n            HttpResponse response = getClient.execute(request);\n            int statusCode = response.getStatusLine().getStatusCode();\n            if (statusCode != HttpStatus.SC_OK){\n                System.out.println(\"login failure code is:\"+statusCode);\n                return null;\n            }\n            int len = (int)response.getEntity().getContentLength();\n            byte[] buf = new byte[len];\n            InputStream inStream = response.getEntity().getContent();\n            int pos = 0;\n            while (pos < len) {\n                int n = inStream.read(buf, pos, len - pos);\n                if (n == -1) {\n                    break;\n                }\n                pos += n;\n            }\n            inStream.close();\n            if (pos != len) {\n                return null;\n            }\n            String txt = new String(buf, \"UTF-8\");\n            JSONObject jsonObject = new JSONObject(txt);\n            String accessToken = jsonObject.getString(\"token\");\n            return accessToken;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    private static final char HEX_DIGITS[] = {\n            '0',\n            '1',\n            '2',\n            '3',\n            '4',\n            '5',\n            '6',\n            '7',\n            '8',\n            '9',\n            'A',\n            'B',\n            'C',\n            'D',\n            'E',\n            'F'\n    };\n\n    public final static String bin2Hex(byte[] b) {\n        StringBuilder sb = new StringBuilder(b.length * 2);\n        for (int i = 0; i < b.length; i++) {\n            sb.append(HEX_DIGITS[(b[i] & 0xf0) >>> 4]);\n            sb.append(HEX_DIGITS[b[i] & 0x0f]);\n        }\n        return sb.toString();\n    }\n\n\n}\n"
  },
  {
    "path": "room_demo/src/io/gobelieve/im/demo/RoomActivity.java",
    "content": "/*                                                                            \n  Copyright (c) 2014-2019, GoBelieve     \n    All rights reserved.\t\t    \t\t\t\t     \t\t\t\n \n  This source code is licensed under the BSD-style license found in the\n  LICENSE file in the root directory of this source tree. An additional grant\n  of patent rights can be found in the PATENTS file in the same directory.\n*/\n\n\npackage io.gobelieve.im.demo;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.util.Log;\nimport com.beetle.bauhinia.MessageActivity;\nimport com.beetle.bauhinia.db.IMessage;\nimport com.beetle.bauhinia.db.message.MessageContent;\nimport com.beetle.bauhinia.db.message.Text;\nimport com.beetle.im.IMService;\nimport com.beetle.im.IMServiceObserver;\nimport com.beetle.im.RoomMessage;\nimport com.beetle.im.RoomMessageObserver;\n\nimport java.util.List;\n\npublic class RoomActivity extends MessageActivity implements RoomMessageObserver, IMServiceObserver {\n\n    private long currentUID;\n    private long roomID;\n    private int msgLocalID = 1;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        Intent intent = getIntent();\n\n        currentUID = intent.getLongExtra(\"current_uid\", 0);\n        if (currentUID == 0) {\n            Log.e(TAG, \"current uid is 0\");\n            return;\n        }\n\n\n        roomID = intent.getLongExtra(\"room_id\", 0);\n        if (roomID == 0) {\n            return;\n        }\n\n        String name = intent.getStringExtra(\"room_name\");\n        if (name == null) {\n            Log.e(TAG, \"peer name is null\");\n            return;\n        }\n\n        getSupportActionBar().setTitle(name);\n        IMService.getInstance().addRoomObserver(this);\n        IMService.getInstance().addObserver(this);\n        IMService.getInstance().enterRoom(roomID);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n\n        IMService.getInstance().leaveRoom(roomID);\n        IMService.getInstance().removeRoomObserver(this);\n        IMService.getInstance().removeObserver(this);\n    }\n\n    @Override\n    public void onRoomMessage(RoomMessage msg) {\n        if (msg.receiver != roomID) {\n            return;\n        }\n        Log.i(TAG, \"recv msg:\" + msg.content);\n        final IMessage imsg = new IMessage();\n        imsg.timestamp = now();\n        imsg.msgLocalID = msgLocalID++;\n        imsg.sender = msg.sender;\n        imsg.receiver = msg.receiver;\n        imsg.setContent(msg.content);\n        insertMessage(imsg);\n    }\n\n    @Override\n    public void onConnectState(IMService.ConnectState state) {\n        if (state == IMService.ConnectState.STATE_CONNECTED) {\n            enableSend();\n        } else {\n            disableSend();\n        }\n    }\n\n\n\n    protected void saveMessage(IMessage imsg) {\n        imsg.msgLocalID = msgLocalID++;\n    }\n\n\n\n    @Override\n    protected void sendMessage(IMessage imsg) {\n        RoomMessage rm = new RoomMessage();\n        rm.sender = imsg.sender;\n        rm.receiver = imsg.receiver;\n        rm.content = imsg.content.getRaw();\n        IMService im = IMService.getInstance();\n        im.sendRoomMessageAsync(rm);\n    }\n\n    protected void sendTextMessage(String text, List<Long> at, List<String> atNames) {\n        IMessage imsg = new IMessage();\n        imsg.sender = this.currentUID;\n        imsg.receiver = this.roomID;\n        MessageContent content = Text.newText(text, at, atNames);\n        imsg.setContent(content);\n        imsg.timestamp = now();\n        imsg.isOutgoing = true;\n        saveMessage(imsg);\n        loadUserName(imsg);\n\n        sendMessage(imsg);\n        imsg.setAck(true);\n        insertMessage(imsg);\n    }\n\n\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "include  ':asynctcp', ':imsdk', ':imlib', ':imkit', ':app'\n"
  }
]