[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": "AppSocket/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "AppSocket/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'com.jakewharton.butterknife'\n\nandroid {\n    compileSdkVersion 26\n    defaultConfig {\n        minSdkVersion 21\n        targetSdkVersion 26\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\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 'com.android.support:appcompat-v7:26.1.0'\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'com.android.support.test:runner:1.0.1'\n    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'\n\n    compile 'com.jakewharton:butterknife:8.5.1'\n    annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'\n}\n"
  },
  {
    "path": "AppSocket/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "AppSocket/src/androidTest/java/melo/com/androidsocket/ExampleInstrumentedTest.java",
    "content": "package melo.com.androidsocket;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() throws Exception {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getTargetContext();\n\n        assertEquals(\"melo.com.androidsocket\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "AppSocket/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"melo.com.androidsocket\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\" />\n</manifest>\n"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/bean/Users.java",
    "content": "package melo.com.androidsocket.bean;\n\n\npublic class Users {\n\n    private int softVersion;\n    private int romVersion;\n    private String imei;\n    private String device;\n    private String ip;\n    private String loginTime;\n\n    public int getSoftVersion() {\n        return softVersion;\n    }\n\n    public void setSoftVersion(int softVersion) {\n        this.softVersion = softVersion;\n    }\n\n    public int getRomVersion() {\n        return romVersion;\n    }\n\n    public void setRomVersion(int romVersion) {\n        this.romVersion = romVersion;\n    }\n\n    public String getImei() {\n        return imei;\n    }\n\n    public void setImei(String imei) {\n        this.imei = imei;\n    }\n\n    public String getDevice() {\n        return device;\n    }\n\n    public void setDevice(String device) {\n        this.device = device;\n    }\n\n    public String getIp() {\n        return ip;\n    }\n\n    public void setIp(String ip) {\n        this.ip = ip;\n    }\n\n    public String getLoginTime() {\n        return loginTime;\n    }\n\n    public void setLoginTime(String loginTime) {\n        this.loginTime = loginTime;\n    }\n}\n"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/common/Config.java",
    "content": "package melo.com.androidsocket.common;\n\n/**\n * Created by melo on 2017/11/27.\n */\n\npublic class Config {\n\n    public static final String MSG = \"msg\";\n    public static final String HEARTBREAK = \"heartbreak\";\n    public static final String PING = \"ping\";\n\n    public static final String TCP_IP = \"ip\";\n    public static final String TCP_PORT = \"port\";\n\n    // 单个CPU线程池大小\n    public static final int POOL_SIZE = 5;\n\n    /**\n     * 错误处理\n     */\n    public static class ErrorCode {\n\n        public static final int CREATE_TCP_ERROR = 1;\n\n        public static final int PING_TCP_TIMEOUT = 2;\n    }\n\n}\n"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/listener/OnConnectionStateListener.java",
    "content": "package melo.com.androidsocket.listener;\n\n/**\n * Created by melo on 2017/11/29.\n */\n\npublic interface OnConnectionStateListener {\n    void onSuccess();\n\n    void onFailed(int errorCode);\n}\n"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/listener/OnMessageReceiveListener.java",
    "content": "package melo.com.androidsocket.listener;\n\n/**\n * Created by melo on 2017/11/27.\n */\n\npublic interface OnMessageReceiveListener {\n    void onMessageReceived(String message);\n}\n"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/socket/SocketManager.java",
    "content": "package melo.com.androidsocket.socket;\n\nimport android.content.Context;\nimport android.text.TextUtils;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport melo.com.androidsocket.common.Config;\nimport melo.com.androidsocket.listener.OnConnectionStateListener;\nimport melo.com.androidsocket.listener.OnMessageReceiveListener;\nimport melo.com.androidsocket.socket.tcp.TCPSocket;\nimport melo.com.androidsocket.socket.udp.UDPSocket;\n\n/**\n * Created by melo on 2017/11/27.\n */\n\npublic class SocketManager {\n\n    private static volatile SocketManager instance = null;\n    private UDPSocket udpSocket;\n    private TCPSocket tcpSocket;\n    private Context mContext;\n\n    private SocketManager(Context context) {\n        mContext = context.getApplicationContext();\n    }\n\n    public static SocketManager getInstance(Context context) {\n        // if already inited, no need to get lock everytime\n        if (instance == null) {\n            synchronized (SocketManager.class) {\n                if (instance == null) {\n                    instance = new SocketManager(context);\n                }\n            }\n        }\n\n        return instance;\n    }\n\n    public void startUdpConnection() {\n        if (udpSocket == null) {\n            udpSocket = new UDPSocket(mContext);\n        }\n\n        // 注册接收消息的接口\n        udpSocket.addOnMessageReceiveListener(new OnMessageReceiveListener() {\n            @Override\n            public void onMessageReceived(String message) {\n                handleUdpMessage(message);\n            }\n        });\n\n        udpSocket.startUDPSocket();\n\n    }\n\n    /**\n     * 处理 udp 收到的消息\n     *\n     * @param message\n     */\n    private void handleUdpMessage(String message) {\n        try {\n            JSONObject jsonObject = new JSONObject(message);\n            String ip = jsonObject.optString(Config.TCP_IP);\n            String port = jsonObject.optString(Config.TCP_PORT);\n            if (!TextUtils.isEmpty(ip) && !TextUtils.isEmpty(port)) {\n                startTcpConnection(ip, port);\n            }\n        } catch (JSONException e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * 开始 TCP 连接\n     *\n     * @param ip\n     * @param port\n     */\n    private void startTcpConnection(String ip, String port) {\n        if (tcpSocket == null) {// 保证收到消息后，只创建一次\n            tcpSocket = new TCPSocket(mContext);\n            tcpSocket.startTcpSocket(ip, port);\n\n            tcpSocket.setOnConnectionStateListener(new OnConnectionStateListener() {\n                @Override\n                public void onSuccess() {// tcp 创建成功\n                    udpSocket.stopHeartbeatTimer();\n                }\n\n                @Override\n                public void onFailed(int errorCode) {// tcp 异常处理\n                    switch (errorCode) {\n                        case Config.ErrorCode.CREATE_TCP_ERROR:\n                            break;\n                        case Config.ErrorCode.PING_TCP_TIMEOUT:\n                            udpSocket.startHeartbeatTimer();\n                            tcpSocket = null;\n                            break;\n                    }\n                }\n            });\n        }\n\n    }\n\n    public void stopSocket() {\n        udpSocket.stopUDPSocket();\n        tcpSocket.stopTcpConnection();\n\n        if (udpSocket != null) {\n            udpSocket = null;\n        }\n        if (tcpSocket != null) {\n            tcpSocket = null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/socket/tcp/TCPSocket.java",
    "content": "package melo.com.androidsocket.socket.tcp;\n\nimport android.content.Context;\nimport android.util.Log;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.PrintWriter;\nimport java.net.Socket;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport melo.com.androidsocket.common.Config;\nimport melo.com.androidsocket.listener.OnConnectionStateListener;\nimport melo.com.androidsocket.utils.HeartbeatTimer;\n\n/**\n * Created by melo on 2017/11/28.\n */\n\npublic class TCPSocket {\n\n    private static final String TAG = \"TCPSocket\";\n\n    private Context mContext;\n    private ExecutorService mThreadPool;\n    private Socket mSocket;\n    private BufferedReader br;\n    private PrintWriter pw;\n    private HeartbeatTimer timer;\n    private long lastReceiveTime = 0;\n\n    private OnConnectionStateListener mListener;\n\n    private static final long TIME_OUT = 15 * 1000;\n    private static final long HEARTBEAT_MESSAGE_DURATION = 2 * 1000;\n\n\n    public TCPSocket(Context context) {\n        this.mContext = context;\n\n        int cpuNumbers = Runtime.getRuntime().availableProcessors();\n        // 根据CPU数目初始化线程池\n        mThreadPool = Executors.newFixedThreadPool(cpuNumbers * Config.POOL_SIZE);\n        // 记录创建对象时的时间\n        lastReceiveTime = System.currentTimeMillis();\n    }\n\n    public void startTcpSocket(final String ip, final String port) {\n        mThreadPool.execute(new Runnable() {\n            @Override\n            public void run() {\n                if (startTcpConnection(ip, Integer.valueOf(port))) {// 尝试建立 TCP 连接\n                    if (mListener != null) {\n                        mListener.onSuccess();\n                    }\n                    startReceiveTcpThread();\n                    startHeartbeatTimer();\n                } else {\n                    if (mListener != null) {\n                        mListener.onFailed(Config.ErrorCode.CREATE_TCP_ERROR);\n                    }\n                }\n            }\n        });\n    }\n\n    public void setOnConnectionStateListener(OnConnectionStateListener listener) {\n        this.mListener = listener;\n    }\n\n    /**\n     * 创建接收线程\n     */\n    private void startReceiveTcpThread() {\n        mThreadPool.execute(new Runnable() {\n            @Override\n            public void run() {\n                String line = \"\";\n                try {\n                    while ((line = br.readLine()) != null) {\n                        handleReceiveTcpMessage(line);\n                    }\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n    }\n\n    /**\n     * 处理 tcp 收到的消息\n     *\n     * @param line\n     */\n    private void handleReceiveTcpMessage(String line) {\n        Log.d(TAG, \"接收 tcp 消息：\" + line);\n        lastReceiveTime = System.currentTimeMillis();\n    }\n\n    private void sendTcpMessage(String json) {\n        pw.println(json);\n        Log.d(TAG, \"tcp 消息发送成功...\");\n    }\n\n    /**\n     * 启动心跳\n     */\n    private void startHeartbeatTimer() {\n        if (timer == null) {\n            timer = new HeartbeatTimer();\n        }\n        timer.setOnScheduleListener(new HeartbeatTimer.OnScheduleListener() {\n            @Override\n            public void onSchedule() {\n                Log.d(TAG, \"timer is onSchedule...\");\n                long duration = System.currentTimeMillis() - lastReceiveTime;\n                Log.d(TAG, \"duration:\" + duration);\n                if (duration > TIME_OUT) {//若超过十五秒都没收到我的心跳包，则认为对方不在线。\n                    Log.d(TAG, \"tcp ping 超时，对方已经下线\");\n                    stopTcpConnection();\n                    if (mListener != null) {\n                        mListener.onFailed(Config.ErrorCode.PING_TCP_TIMEOUT);\n                    }\n                } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//若超过两秒他没收到我的心跳包，则重新发一个。\n                    JSONObject jsonObject = new JSONObject();\n                    try {\n                        jsonObject.put(Config.MSG, Config.PING);\n                    } catch (JSONException e) {\n                        e.printStackTrace();\n                    }\n                    sendTcpMessage(jsonObject.toString());\n                }\n            }\n\n        });\n        timer.startTimer(0, 1000 * 2);\n    }\n\n    public void stopHeartbeatTimer() {\n        if (timer != null) {\n            timer.exit();\n            timer = null;\n        }\n    }\n\n    /**\n     * 尝试建立tcp连接\n     *\n     * @param ip\n     * @param port\n     */\n    private boolean startTcpConnection(final String ip, final int port) {\n        try {\n            if (mSocket == null) {\n                mSocket = new Socket(ip, port);\n                mSocket.setKeepAlive(true);\n                mSocket.setTcpNoDelay(true);\n                mSocket.setReuseAddress(true);\n            }\n            InputStream is = mSocket.getInputStream();\n            br = new BufferedReader(new InputStreamReader(is));\n            OutputStream os = mSocket.getOutputStream();\n            pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)), true);\n            Log.d(TAG, \"tcp 创建成功...\");\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return false;\n    }\n\n    public void stopTcpConnection() {\n        try {\n            stopHeartbeatTimer();\n            if (br != null) {\n                br.close();\n            }\n            if (pw != null) {\n                pw.close();\n            }\n            if (mThreadPool != null) {\n                mThreadPool.shutdown();\n                mThreadPool = null;\n            }\n            if (mSocket != null) {\n                mSocket.close();\n                mSocket = null;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n}\n"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/socket/udp/UDPSocket.java",
    "content": "package melo.com.androidsocket.socket.udp;\n\nimport android.content.Context;\nimport android.util.Log;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.IOException;\nimport java.net.DatagramPacket;\nimport java.net.DatagramSocket;\nimport java.net.InetAddress;\nimport java.net.SocketException;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport melo.com.androidsocket.bean.Users;\nimport melo.com.androidsocket.common.Config;\nimport melo.com.androidsocket.listener.OnMessageReceiveListener;\nimport melo.com.androidsocket.utils.DeviceUtil;\nimport melo.com.androidsocket.utils.HeartbeatTimer;\nimport melo.com.androidsocket.utils.WifiUtil;\n\n\n/**\n * Created by melo on 2017/9/20.\n */\n\npublic class UDPSocket {\n\n    private static final String TAG = \"UDPSocket\";\n\n    private static final int BUFFER_LENGTH = 1024;\n    private byte[] receiveByte = new byte[BUFFER_LENGTH];\n\n    private static String BROADCAST_IP = \"192.168.43.255\";\n\n    // 端口号，飞鸽协议默认端口2425\n    public static final int CLIENT_PORT = 2425;\n\n    private boolean isThreadRunning = false;\n\n    private Context mContext;\n    private DatagramSocket client;\n    private DatagramPacket receivePacket;\n\n    private long lastReceiveTime = 0;\n    private static final long TIME_OUT = 120 * 1000;\n    private static final long HEARTBEAT_MESSAGE_DURATION = 5 * 1000;\n\n    private ExecutorService mThreadPool;\n    private Thread clientThread;\n    private HeartbeatTimer timer;\n    private Users localUser;\n    private Users remoteUser;\n    private final List<OnMessageReceiveListener> messageReceiveList;\n\n    public UDPSocket(Context context) {\n\n        this.mContext = context;\n\n        int cpuNumbers = Runtime.getRuntime().availableProcessors();\n        // 根据CPU数目初始化线程池\n        mThreadPool = Executors.newFixedThreadPool(cpuNumbers * Config.POOL_SIZE);\n        // 记录创建对象时的时间\n        lastReceiveTime = System.currentTimeMillis();\n\n        messageReceiveList = new ArrayList<>();\n\n        Log.d(TAG, \"创建 UDP 对象\");\n//        createUser();\n    }\n\n    public void addOnMessageReceiveListener(OnMessageReceiveListener listener) {\n        messageReceiveList.add(listener);\n    }\n\n    /**\n     * 创建本地用户信息\n     */\n    private void createUser() {\n        if (localUser == null) {\n            localUser = new Users();\n        }\n        if (remoteUser == null) {\n            remoteUser = new Users();\n        }\n\n        localUser.setImei(DeviceUtil.getDeviceId(mContext));\n        localUser.setSoftVersion(DeviceUtil.getPackageVersionCode(mContext));\n\n        if (WifiUtil.getInstance(mContext).isWifiApEnabled()) {// 判断当前是否是开启热点方\n            localUser.setIp(\"192.168.43.1\");\n        } else {// 当前是开启 wifi 方\n            localUser.setIp(WifiUtil.getInstance(mContext).getLocalIPAddress());\n            remoteUser.setIp(WifiUtil.getInstance(mContext).getServerIPAddress());\n        }\n    }\n\n\n    public void startUDPSocket() {\n        if (client != null) return;\n        try {\n            // 表明这个 Socket 在设置的端口上监听数据。\n            client = new DatagramSocket(CLIENT_PORT);\n            client.setReuseAddress(true);\n            if (receivePacket == null) {\n                // 创建接受数据的 packet\n                receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);\n            }\n\n            startSocketThread();\n        } catch (SocketException e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * 开启接收数据的线程\n     */\n    private void startSocketThread() {\n        clientThread = new Thread(new Runnable() {\n            @Override\n            public void run() {\n                receiveMessage();\n            }\n        });\n        isThreadRunning = true;\n        clientThread.start();\n        Log.d(TAG, \"开启 UDP 数据接收线程\");\n\n        startHeartbeatTimer();\n    }\n\n    /**\n     * 处理接受到的消息\n     */\n    private void receiveMessage() {\n        while (isThreadRunning) {\n            try {\n                if (client != null) {\n                    client.receive(receivePacket);\n                }\n                lastReceiveTime = System.currentTimeMillis();\n                Log.d(TAG, \"receive packet success...\");\n            } catch (IOException e) {\n                Log.e(TAG, \"UDP数据包接收失败！线程停止\");\n                stopUDPSocket();\n                e.printStackTrace();\n                return;\n            }\n\n            if (receivePacket == null || receivePacket.getLength() == 0) {\n                Log.e(TAG, \"无法接收UDP数据或者接收到的UDP数据为空\");\n                continue;\n            }\n\n            String strReceive = new String(receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength());\n            Log.d(TAG, strReceive + \" from \" + receivePacket.getAddress().getHostAddress() + \":\" + receivePacket.getPort());\n\n            //解析接收到的 json 信息\n            notifyMessageReceive(strReceive);\n            // 每次接收完UDP数据后，重置长度。否则可能会导致下次收到数据包被截断。\n            if (receivePacket != null) {\n                receivePacket.setLength(BUFFER_LENGTH);\n            }\n        }\n    }\n\n    /**\n     * 将消息通过接口发送到每个页面\n     *\n     * @param strReceive\n     */\n    private void notifyMessageReceive(String strReceive) {\n        for (OnMessageReceiveListener listener : messageReceiveList) {\n            if (listener != null) {\n                listener.onMessageReceived(strReceive);\n            }\n        }\n    }\n\n    public void stopUDPSocket() {\n        isThreadRunning = false;\n        receivePacket = null;\n        stopHeartbeatTimer();\n        if (clientThread != null) {\n            clientThread.interrupt();\n        }\n        if (mThreadPool != null) {\n            mThreadPool.shutdown();\n        }\n        if (client != null) {\n            client.close();\n            client = null;\n        }\n        if (timer != null) {\n            timer.exit();\n        }\n    }\n\n    /**\n     * 启动心跳，timer 间隔十秒\n     */\n    public void startHeartbeatTimer() {\n        if (timer == null) {\n            timer = new HeartbeatTimer();\n        }\n        timer.setOnScheduleListener(new HeartbeatTimer.OnScheduleListener() {\n            @Override\n            public void onSchedule() {\n                Log.d(TAG, \"timer is onSchedule...\");\n                long duration = System.currentTimeMillis() - lastReceiveTime;\n                Log.d(TAG, \"duration:\" + duration);\n                if (duration > TIME_OUT) {//若超过两分钟都没收到我的心跳包，则认为对方不在线。\n                    Log.d(TAG, \"超时，对方已经下线\");\n                    // 刷新时间，重新进入下一个心跳周期\n                    lastReceiveTime = System.currentTimeMillis();\n                } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//若超过十秒他没收到我的心跳包，则重新发一个。\n                    JSONObject jsonObject = new JSONObject();\n                    try {\n                        jsonObject.put(Config.MSG, Config.HEARTBREAK);\n                    } catch (JSONException e) {\n                        e.printStackTrace();\n                    }\n                    sendMessage(jsonObject.toString());\n                }\n            }\n\n        });\n        timer.startTimer(0, 1000 * 5);\n    }\n\n    public void stopHeartbeatTimer() {\n        if (timer != null) {\n            timer.exit();\n            timer = null;\n        }\n    }\n\n    /**\n     * 发送心跳包\n     *\n     * @param message\n     */\n    public void sendMessage(final String message) {\n        mThreadPool.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    BROADCAST_IP = WifiUtil.getBroadcastAddress();\n                    Log.d(TAG, \"BROADCAST_IP:\" + BROADCAST_IP);\n                    InetAddress targetAddress = InetAddress.getByName(BROADCAST_IP);\n\n                    DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), targetAddress, CLIENT_PORT);\n\n                    client.send(packet);\n\n                    // 数据发送事件\n                    Log.d(TAG, \"数据发送成功\");\n\n                } catch (UnknownHostException e) {\n                    e.printStackTrace();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n\n            }\n        });\n    }\n\n\n}\n"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/utils/DeviceUtil.java",
    "content": "package melo.com.androidsocket.utils;\n\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport android.net.wifi.WifiInfo;\nimport android.net.wifi.WifiManager;\nimport android.os.PowerManager;\nimport android.provider.Settings;\nimport android.provider.Settings.SettingNotFoundException;\nimport android.telephony.TelephonyManager;\nimport android.text.TextUtils;\nimport android.util.DisplayMetrics;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileFilter;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.lang.reflect.Method;\nimport java.net.NetworkInterface;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\n\n/**\n * 获取设备的信息\n *\n * @author melo\n */\npublic final class DeviceUtil {\n\n    /**\n     * <p><b>IMEI.</b></p> Returns the unique device ID, for example, the IMEI for GSM and the MEID\n     * or ESN for CDMA phones. Return null if device ID is not available.\n     * <p>\n     * Requires Permission: READ_PHONE_STATE\n     *\n     * @param context\n     * @return\n     */\n    public synchronized static String getDeviceId(Context context) {\n        if (context == null) {\n            return \"\";\n        }\n\n        String imei = \"\";\n\n        try {\n            TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);\n            if (tm == null || TextUtils.isEmpty(tm.getDeviceId())) {\n                // 双卡双待需要通过phone1和phone2获取imei，默认取phone1的imei。\n                tm = (TelephonyManager) context.getSystemService(\"phone1\");\n            }\n\n            if (tm != null) {\n                imei = tm.getDeviceId();\n            }\n        } catch (SecurityException e) {\n            e.printStackTrace();\n        }\n\n\n        return imei;\n    }\n\n    /**\n     * Returns the serial number of the SIM, if applicable. Return null if it is\n     * unavailable.\n     * <p>\n     * Requires Permission: READ_PHONE_STATE\n     *\n     * @param context\n     * @return\n     */\n    public synchronized static String getSimSerialNumber(Context context) {\n        if (context == null) {\n            return \"\";\n        }\n        final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);\n        return tm.getSimSerialNumber();\n    }\n\n    /**\n     * A 64-bit number (as a hex string) that is randomly generated on the\n     * device's first boot and should remain constant for the lifetime of the\n     * device. (The value may change if a factory reset is performed on the\n     * device.)\n     *\n     * @param context\n     * @return\n     */\n    public synchronized static String getAndroidID(Context context) {\n        return Settings.Secure.getString(context.getContentResolver(),\n                Settings.Secure.ANDROID_ID);\n    }\n\n    /**\n     * 操作系统版本\n     *\n     * @return\n     */\n    public static String getOSversion() {\n        return android.os.Build.VERSION.RELEASE;\n    }\n\n    /**\n     * 设备商\n     *\n     * @return\n     */\n    public static String getManufacturer() {\n        return android.os.Build.MANUFACTURER;\n    }\n\n    /**\n     * 设备型号\n     *\n     * @return\n     */\n    public static String getModel() {\n        return android.os.Build.MODEL;\n    }\n\n    /**\n     * 序列号\n     *\n     * @return\n     */\n    public static String getSerialNumber() {\n        String serial = null;\n        try {\n            Class<?> c = Class.forName(\"android.os.SystemProperties\");\n            Method get = c.getMethod(\"get\", String.class);\n            serial = (String) get.invoke(c, \"ro.serialno\");\n        } catch (Exception ignored) {\n        }\n        return serial;\n    }\n\n    /**\n     * SD CARD ID\n     *\n     * @return\n     */\n    public static synchronized String getSDcardID() {\n        try {\n            String sdCid = null;\n            String[] memBlkArray = new String[]{\"/sys/block/mmcblk0\", \"/sys/block/mmcblk1\", \"/sys/block/mmcblk2\"};\n            for (String memBlk : memBlkArray) {\n                File file = new File(memBlk);\n                if (file.exists() && file.isDirectory()) {\n                    Process cmd = Runtime.getRuntime().exec(\"cat \" + memBlk + \"/device/cid\");\n                    BufferedReader br = new BufferedReader(new InputStreamReader(cmd.getInputStream()));\n                    sdCid = br.readLine();\n                    if (!TextUtils.isEmpty(sdCid)) {\n                        return sdCid;\n                    }\n                }\n            }\n            return null;\n        } catch (IOException e) {\n            // TODO Auto-generated catch block\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    /**\n     * 获取mac地址\n     *\n     * @param context\n     * @return\n     */\n    public static String getMac(Context context) {\n        if (context == null) {\n            return \"\";\n        }\n        String mac = null;\n        try {\n            final WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);\n            if (wifi != null) {\n                WifiInfo info = wifi.getConnectionInfo();\n                if (null != info && info.getMacAddress() != null) {\n                    mac = info.getMacAddress();\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return mac;\n    }\n\n    /**\n     * 获取mac地址\n     * 可以突破android6.0的限制\n     *\n     * @return\n     */\n    public static String getWifiMacAddress() {\n        try {\n            String interfaceName = \"wlan0\";\n            List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());\n            for (NetworkInterface intf : interfaces) {\n                if (!intf.getName().equalsIgnoreCase(interfaceName)) {\n                    continue;\n                }\n\n                byte[] mac = intf.getHardwareAddress();\n                if (mac == null) {\n                    return \"\";\n                }\n\n                StringBuilder buf = new StringBuilder();\n                for (byte aMac : mac) {\n                    buf.append(String.format(\"%02X:\", aMac));\n                }\n                if (buf.length() > 0) {\n                    buf.deleteCharAt(buf.length() - 1);\n                }\n                return buf.toString();\n            }\n        } catch (Exception ex) {\n        } // for now eat exceptions\n        return \"\";\n    }\n\n    /**\n     * 获取IMSI\n     *\n     * @param context\n     * @return\n     */\n    public static String getIMSI(Context context) {\n\n        TelephonyManager tm = (TelephonyManager) context\n                .getSystemService(Context.TELEPHONY_SERVICE);\n\n        return tm.getSubscriberId();\n\n    }\n\n    /**\n     * get sim serial number\n     */\n    public static String getSimSerialNum(Context context) {\n        TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);\n        return tm.getSimSerialNumber();\n    }\n\n    /**\n     * 获取屏幕的分辨率\n     *\n     * @param context\n     * @return int array with 2 items. The first item is width, and the second is height.\n     */\n    public static int[] getScreenResolution(Context context) {\n        DisplayMetrics dm = context.getResources().getDisplayMetrics();\n\n        int[] resolution = new int[2];\n        resolution[0] = dm.widthPixels;\n        resolution[1] = dm.heightPixels;\n\n        return resolution;\n    }\n\n    /**\n     * 获取WIFI的Mac地址\n     *\n     * @param context\n     * @return Wifi的BSSID即mac地址\n     */\n    public static String getWifiBSSID(Context context) {\n        if (context == null) {\n            return null;\n        }\n\n        String mac = null;\n        WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);\n        WifiInfo info = wm.getConnectionInfo();\n        if (info != null) {\n            mac = info.getBSSID();// 获得本机的MAC地址\n        }\n\n        return mac;\n    }\n\n    public static String getPackageVersion(Context context) {\n        PackageManager packageManager = context.getPackageManager();\n        PackageInfo packInfo;\n        try {\n            packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);\n            return packInfo.versionName;\n        } catch (NameNotFoundException e) {\n            e.printStackTrace();\n        }\n\n        return null;\n    }\n\n    public static int getPackageVersionCode(Context context) {\n        PackageManager packageManager = context.getPackageManager();\n        PackageInfo packInfo;\n        try {\n            packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);\n            return packInfo.versionCode;\n        } catch (NameNotFoundException e) {\n            e.printStackTrace();\n        }\n\n        return 0;\n    }\n\n    /**\n     * 获取系统休眠时间。\n     *\n     * @return\n     */\n    public static int getScreenOffTimeOut(Context context) {\n        int sleepTime;\n        try {\n            sleepTime = Settings.System.getInt(context.getContentResolver(), Settings.System.SCREEN_OFF_TIMEOUT);\n        } catch (SettingNotFoundException e) {\n            e.printStackTrace();\n            sleepTime = 15 * 1000;\n        }\n        return sleepTime;\n    }\n\n\n    public static boolean isScreenOn(Context context) {\n        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);\n        return powerManager.isScreenOn();\n    }\n\n    /**\n     * Gets the number of cores available in this device, across all processors.\n     * Requires: Ability to peruse the filesystem at \"/sys/devices/system/cpu\"\n     *\n     * @return The number of cores, or 1 if failed to get result\n     */\n    public static int getCPUNumCores() {\n        try {\n            //Get directory containing CPU info\n            File dir = new File(\"/sys/devices/system/cpu/\");\n            //Filter to only list the devices we care about\n            File[] files = dir.listFiles(new FileFilter() {\n                @Override\n                public boolean accept(File pathname) {\n                    //Check if filename is \"cpu\", followed by a single digit number\n                    if (Pattern.matches(\"cpu[0-9]\", pathname.getName())) {\n                        return true;\n                    }\n                    return false;\n                }\n            });\n            //Return the number of cores (virtual CPU devices)\n            return files.length;\n        } catch (Exception e) {\n            return 1;\n        }\n    }\n\n    /**\n     * 获取系统参数\n     *\n     * @param configName\n     * @return\n     */\n\n    public static String getSystemConf(String configName) {\n        try {\n            Process process = Runtime.getRuntime().exec(\"getprop \" + configName);\n            InputStreamReader ir = new InputStreamReader(process.getInputStream());\n            BufferedReader input = new BufferedReader(ir);\n            String value = input.readLine();\n            input.close();\n            ir.close();\n            process.destroy();\n            return value;\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return \"\";\n    }\n\n    /**\n     * 获取硬件版本\n     *\n     * @return\n     */\n    public static String getHardwareVersion() {\n        return getSystemConf(\"ro.hardware\");\n    }\n\n    /**\n     * 获取rom版本\n     */\n    public static String getRomVersion() {\n        return getSystemConf(\"ro.mediatek.version.release\");\n    }\n\n    /**\n     * 获取hq rom版本\n     */\n    private static String gethqRomVersion() {\n        return getSystemConf(\"ro.huaqin.version.release\");\n    }\n\n    public static String getShowhqRomVersion() {\n        String showHq = \"hq\";\n        String hqRomVer = gethqRomVersion();\n        if (TextUtils.isEmpty(hqRomVer) == false) {\n            String[] s = hqRomVer.split(\"_\");\n            if (s != null && s.length >= 3) {\n                showHq = s[2];\n            }\n        }\n        return showHq;\n    }\n\n    /**\n     * 获取installed apk版本\n     */\n    public static PackageInfo getInstalledAppInfo(Context context, String pname) {\n        try {\n            List<PackageInfo> packages = context.getPackageManager().getInstalledPackages(0);\n            if (packages != null) {\n                for (PackageInfo pinfo : packages) {\n                    if (pinfo != null && pinfo.packageName.equals(pname)) {\n                        return pinfo;\n                    }\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n}"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/utils/HeartbeatTimer.java",
    "content": "package melo.com.androidsocket.utils;\n\nimport java.util.Timer;\nimport java.util.TimerTask;\n\n/**\n * Created by melo on 2017/9/21.\n */\n\npublic class HeartbeatTimer {\n\n    private Timer timer;\n    private TimerTask task;\n    private OnScheduleListener mListener;\n\n    public HeartbeatTimer() {\n        timer = new Timer();\n    }\n\n    public void startTimer(long delay, long period) {\n        task = new TimerTask() {\n            @Override\n            public void run() {\n                if (mListener != null) {\n                    mListener.onSchedule();\n                }\n            }\n        };\n        timer.schedule(task, delay, period);\n    }\n\n    public void exit() {\n        if (task != null) {\n            task.cancel();\n        }\n        if (timer != null) {\n            timer.cancel();\n        }\n    }\n\n    public interface OnScheduleListener {\n        void onSchedule();\n    }\n\n    public void setOnScheduleListener(OnScheduleListener listener) {\n        this.mListener = listener;\n    }\n}\n"
  },
  {
    "path": "AppSocket/src/main/java/melo/com/androidsocket/utils/WifiUtil.java",
    "content": "package melo.com.androidsocket.utils;\n\nimport android.content.Context;\nimport android.net.DhcpInfo;\nimport android.net.wifi.WifiInfo;\nimport android.net.wifi.WifiManager;\n\nimport java.lang.reflect.Method;\nimport java.net.InetAddress;\nimport java.net.InterfaceAddress;\nimport java.net.NetworkInterface;\nimport java.util.Enumeration;\nimport java.util.Iterator;\nimport java.util.List;\n\n/**\n * Created by melo on 2017/9/23.\n */\n\npublic class WifiUtil {\n\n    private static final String TAG = \"LocationUtils\";\n\n    private static volatile WifiUtil instance = null;\n\n    private WifiManager mWifiManager;\n\n    private Context mContext;\n\n    private WifiUtil(Context context) {\n        mContext = context;\n        mWifiManager = (WifiManager) mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);\n    }\n\n    public static WifiUtil getInstance(Context context) {\n        if (instance == null) {\n            synchronized (WifiUtil.class) {\n                if (instance == null) {\n                    instance = new WifiUtil(context);\n                }\n            }\n        }\n        return instance;\n    }\n\n    public boolean isWifiApEnabled() {\n        try {\n            Method method = mWifiManager.getClass().getMethod(\"isWifiApEnabled\");\n            method.setAccessible(true);\n            return (Boolean) method.invoke(mWifiManager);\n\n        } catch (NoSuchMethodException e) {\n            // TODO Auto-generated catch block\n            e.printStackTrace();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return false;\n    }\n\n    public String getLocalIPAddress() {\n        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();\n        return intToIp(wifiInfo.getIpAddress());\n    }\n\n    public String getServerIPAddress() {\n        DhcpInfo mDhcpInfo = mWifiManager.getDhcpInfo();\n        return intToIp(mDhcpInfo.gateway);\n    }\n\n    private static String intToIp(int i) {\n        return (i & 0xFF) + \".\" + ((i >> 8) & 0xFF) + \".\" + ((i >> 16) & 0xFF) + \".\"\n                + ((i >> 24) & 0xFF);\n    }\n\n    /**\n     * @return 优先获取网卡地址\n     */\n    public static String getBroadcastAddress() {\n        String broadcast = getBroadcastAddress(\"p2p\");\n        if (broadcast == null) {\n            return getBroadcastAddress(\"wlan0\");\n        }\n        return broadcast;\n    }\n\n    /**\n     * @param netCardName 网卡名称\n     * @return 获取的广播地址\n     */\n    public static String getBroadcastAddress(String netCardName) {\n        try {\n            Enumeration<NetworkInterface> eni = NetworkInterface\n                    .getNetworkInterfaces();\n            while (eni.hasMoreElements()) {\n                NetworkInterface networkCard = eni.nextElement();\n                if (networkCard.getDisplayName().startsWith(netCardName)) {\n                    List<InterfaceAddress> ncAddrList = networkCard\n                            .getInterfaceAddresses();\n                    Iterator<InterfaceAddress> ncAddrIterator = ncAddrList.iterator();\n                    while (ncAddrIterator.hasNext()) {\n                        InterfaceAddress networkCardAddress = ncAddrIterator.next();\n                        InetAddress address = networkCardAddress.getAddress();\n                        if (!address.isLoopbackAddress()) {\n                            String hostAddress = address.getHostAddress();\n                            if (hostAddress.indexOf(\":\") > 0) {\n                                // case : ipv6\n                                continue;\n                            } else {\n                                // case : ipv4\n                                String broadcastAddress = networkCardAddress.getBroadcast().getHostAddress();\n                                return broadcastAddress;\n                            }\n                        }\n                    }\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return null;\n    }\n\n\n}\n"
  },
  {
    "path": "AppSocket/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillColor=\"#26A69A\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n</vector>\n"
  },
  {
    "path": "AppSocket/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"78.5885\"\n                android:endY=\"90.9159\"\n                android:startX=\"48.7653\"\n                android:startY=\"61.0927\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\" />\n</vector>\n"
  },
  {
    "path": "AppSocket/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "AppSocket/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "AppSocket/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "AppSocket/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AndroidSocket</string>\n</resources>\n"
  },
  {
    "path": "AppSocket/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "AppSocket/src/test/java/melo/com/androidsocket/ExampleUnitTest.java",
    "content": "package melo.com.androidsocket;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "README.md",
    "content": "# AndroidSocket\n\n项目介绍地址：\n\nhttp://www.jianshu.com/p/61de9478c9aa\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 26\n\n\n\n    defaultConfig {\n        applicationId \"melo.com.app\"\n        minSdkVersion 21\n        targetSdkVersion 26\n        versionCode 1\n        versionName \"1.0\"\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\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 'com.android.support:appcompat-v7:26.1.0'\n    implementation 'com.android.support.constraint:constraint-layout:1.0.2'\n    implementation 'com.android.support:design:26.1.0'\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'com.android.support.test:runner:1.0.1'\n    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'\n\n    implementation project(':AppSocket')\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "app/src/androidTest/java/melo/com/app/ExampleInstrumentedTest.java",
    "content": "package melo.com.app;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() throws Exception {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getTargetContext();\n\n        assertEquals(\"melo.com.app\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "app/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=\"melo.com.app\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".MainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme.NoActionBar\">\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    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/melo/com/app/MainActivity.java",
    "content": "package melo.com.app;\n\nimport android.os.Bundle;\nimport android.support.design.widget.FloatingActionButton;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.Toolbar;\n\nimport butterknife.BindView;\nimport butterknife.ButterKnife;\nimport butterknife.OnClick;\nimport melo.com.androidsocket.socket.SocketManager;\n\npublic class MainActivity extends AppCompatActivity {\n\n    @BindView(R.id.toolbar)\n    Toolbar toolbar;\n    @BindView(R.id.fab)\n    FloatingActionButton fab;\n    private SocketManager manager;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        ButterKnife.bind(this);\n        manager = SocketManager.getInstance(this);\n        manager.startUdpConnection();\n    }\n\n    @OnClick(R.id.fab)\n    public void onViewClicked() {\n\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        manager.stopSocket();\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillColor=\"#26A69A\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"78.5885\"\n                android:endY=\"90.9159\"\n                android:startX=\"48.7653\"\n                android:startY=\"61.0927\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z\"\n        android:strokeColor=\"#00000000\"\n        android:strokeWidth=\"1\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.design.widget.CoordinatorLayout 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=\"melo.com.app.MainActivity\">\n\n    <android.support.design.widget.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"@style/AppTheme.AppBarOverlay\">\n\n        <android.support.v7.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"?attr/colorPrimary\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\" />\n\n    </android.support.design.widget.AppBarLayout>\n\n    <include layout=\"@layout/content_main\" />\n\n    <android.support.design.widget.FloatingActionButton\n        android:id=\"@+id/fab\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"bottom|end\"\n        android:layout_margin=\"@dimen/fab_margin\"\n        app:srcCompat=\"@android:drawable/ic_dialog_email\" />\n\n</android.support.design.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/content_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.constraint.ConstraintLayout 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    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n    tools:context=\"melo.com.app.MainActivity\"\n    tools:showIn=\"@layout/activity_main\">\n\n    <EditText\n        android:id=\"@+id/et_message\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <Button\n        android:id=\"@+id/bt_send_message\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"24dp\"\n        android:text=\"send\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/et_message\" />\n</android.support.constraint.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_main.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=\"melo.com.app.MainActivity\">\n    <item\n        android:id=\"@+id/action_settings\"\n        android:orderInCategory=\"100\"\n        android:title=\"@string/action_settings\"\n        app:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <dimen name=\"fab_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">App</string>\n    <string name=\"action_settings\">Settings</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"AppTheme.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n\n    <style name=\"AppTheme.AppBarOverlay\" parent=\"ThemeOverlay.AppCompat.Dark.ActionBar\" />\n\n    <style name=\"AppTheme.PopupOverlay\" parent=\"ThemeOverlay.AppCompat.Light\" />\n\n</resources>\n"
  },
  {
    "path": "app/src/test/java/melo/com/app/ExampleUnitTest.java",
    "content": "package melo.com.app;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    \n    repositories {\n        google()\n        jcenter()\n        mavenCentral()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.0.0'\n\n        // ButterKnife\n        classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Nov 27 10:59:32 CST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\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.\norg.gradle.jvmargs=-Xmx1536m\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\n"
  },
  {
    "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# 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\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\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    JAVACMD=`cygpath --unix \"$JAVACMD\"`\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": "settings.gradle",
    "content": "include ':AppSocket', ':app'\n"
  }
]