public interface CallbackEvent {
    public void call(Object... args);
} public class ChannelCore {

    private static final String TAG = ChannelCore.class.getSimpleName();

    private String mChannelId;
    private String mServerUrl;
    private String mServerName;
    private XPushSession xpushSession;

    private String mHost;
    private String mAppId;
    private String mDeviceId;

    private Socket mChannelSocket;

    private HashMap<String, Emitter.Listener> mEvents;

    public ChannelCore() {
        init();
    }

    public ChannelCore(String channelId, String serveUrl, String serverName) {
        this();
        this.mChannelId = channelId;
        this.mServerUrl = serveUrl;
        this.mServerName = serverName;
    }

    public void init() {
        xpushSession = XPushCore.getXpushSession();

        this.mHost = XPushCore.getHostname();
        this.mAppId = XPushCore.getAppId();
        this.mDeviceId = xpushSession.getDeviceId();
    }

    public void connect(HashMap<String, Emitter.Listener> events) {

        mEvents = events;
        String url = mServerUrl + "/channel";

        IO.Options opts = new IO.Options();
        opts.forceNew = true;

        opts.query = "A=" + mAppId + "&C=" + mChannelId + "&S=" + mServerName + "&D=" + mDeviceId + "&U=" + xpushSession.getId();

        mChannelSocket = null;

        try {
            mChannelSocket = IO.socket(url, opts);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }

        if (events != null) {
            for (String eventName : events.keySet()) {
                this.on(eventName, events.get(eventName));
            }
        }

        mChannelSocket.connect();
    }

    public void sendMessage(String message) {
        sendMessage(message, null, null, null);
    }

    public void sendMessage(String message, String type) {
        sendMessage(message, type, null, null);
    }

    public void sendImage(String message, int width, int height){
        JSONObject metaData = new JSONObject();

        try {

            metaData.put("W", width);
            metaData.put("H", height);

        } catch (JSONException e) {
            e.printStackTrace();
        }
        sendMessage(message, "IM", metaData, null );
    }

    public void sendMessage(String message, String type, ArrayList<String> users) {
        sendMessage(message, type, null, users);
    }

    public void sendMessage(String message, String type, JSONObject metaData, ArrayList<String> users) {

        JSONObject json = new JSONObject();
        JSONObject data = new JSONObject();
        JSONObject user = new JSONObject();

        try {

            user.put("I", xpushSession.getImage());
            user.put("U", xpushSession.getId());
            user.put("NM", xpushSession.getName());

            data.put("UO", user);
            data.put("MG", message);
            if( type != null ) {
                data.put("TP", type);
            }

            if( users != null ) {
                data.put("US", TextUtils.join("@!@", users));
            }

            if( metaData != null ){
                data.put("MD", metaData);
            }

            json.put("DT", data);
            json.put("NM", "message");

        } catch (JSONException e) {
            e.printStackTrace();
        }

        if (mChannelSocket != null && mChannelSocket.connected()) {
            mChannelSocket.emit("send", json);
        }
    }

    public void emit(final String event, final Object... args) {
        mChannelSocket.emit(event, args);
    }

    public void on(String event, Emitter.Listener fn) {
        mChannelSocket.on(event, fn);
    }

    public void off(String event) {
        mChannelSocket.off(event);
    }

    public void off(String event, Emitter.Listener fn) {
        mChannelSocket.off(event, fn);
    }

    public boolean connected() {
        if (mChannelSocket == null) {
            return false;
        }

        return mChannelSocket.connected();
    }

    public void disconnect() {
        if (mChannelSocket != null) {
            for (String eventName : mEvents.keySet()) {
                this.off(eventName);
            }
            Log.d(TAG, "=== disconnect ===");
            mChannelSocket.disconnect();
        }
    }

    public void getMessageUnread(long lastReceiveTime, final CallbackEvent callback) {
        if (mChannelSocket != null) {

            JSONObject jsonObject = new JSONObject();
            try {

                if (lastReceiveTime > 0) {
                    jsonObject.put("TS", lastReceiveTime);
                }

            } catch (JSONException e) {
                e.printStackTrace();
            }

            mChannelSocket.emit("message.unread", jsonObject, new Ack() {
                @Override
                public void call(Object... args) {
                    JSONObject response = (JSONObject) args[0];
                    if (response.has("status")) {
                        try {
                            if ("ok".equalsIgnoreCase(response.getString("status"))) {
                                JSONArray messages = (JSONArray) response.get("result");
                                callback.call(messages);
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                            callback.call(new JSONArray());
                        }
                    } else {
                        callback.call(new JSONArray());
                    }
                }
            });
        }
    }


    public void channelJoin(ArrayList<String> userIdArrayList, final CallbackEvent callback) {

        JSONArray userArray = new JSONArray();
        for( String userId : userIdArrayList ){
            userArray.put(userId);
        }

        JSONObject data = new JSONObject();
        try {
            data.put( "U", userArray );
        } catch (JSONException e) {
            e.printStackTrace();
        }

        mChannelSocket.emit("channel.join", data, new Ack() {
            @Override
            public void call(Object... args) {
                JSONObject response = (JSONObject) args[0];

                Log.d(TAG, response.toString() );
                if (response.has("status")) {
                    try {
                        if ("ok".equalsIgnoreCase(response.getString("status"))) {
                            callback.call();
                        } else {
                            callback.call();
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    public void channelGet(final CallbackEvent callback) {

        mChannelSocket.emit("channel.get", new Ack() {
            @Override
            public void call(Object... args) {
                JSONObject response = (JSONObject) args[0];

                Log.d(TAG, response.toString() );
                if (response.has("status")) {
                    try {
                        if ("ok".equalsIgnoreCase(response.getString("status"))) {
                            callback.call(response);
                        } else {
                            callback.call();
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    public void channelLeave(final CallbackEvent callback) {

        mChannelSocket.emit("channel.leave", new Ack() {
            @Override
            public void call(Object... args) {
                JSONObject response = (JSONObject) args[0];

                if (response.has("status")) {
                    try {
                        if ("ok".equalsIgnoreCase(response.getString("status"))) {
                            callback.call();
                        } else {
                            if (response.has("message") && "ERR-NOTEXIST".equals(response.getString("message") ) ) {
                                callback.call();
                            }
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
} public class XPushCore {

    private static final int NETWORK_WIFI = 1;
    private static final int NETWORK_MOBILE = 2;
    private static final int NETWORK_NOT_AVAILABLE = 0;

    private static XPushSession mXpushSession;

    private static final String TAG = XPushCore.class.getSimpleName();

    public static XPushCore sInstance;

    private static String mHostname;
    private static String mAppId;
    private static String mDeviceId;
    private static Context baseContext;

    private static Socket mGlobalSocket;


    private HashMap<String, Emitter.Listener> mEvents;

    private static final AtomicBoolean mGlobalSocketConnected = new AtomicBoolean();
    private static final AtomicBoolean mConnecting = new AtomicBoolean();

    private static CountDownLatch latch = new CountDownLatch(2);

    public static void initialize(Context context){
        if( sInstance == null ){
            sInstance =  new XPushCore(context);
            sInstance.init();
        }
    }

    public static XPushCore getInstance(){

        if( sInstance == null ){
            sInstance =  new XPushCore();
            sInstance.init();
        }

        return sInstance;
    }

    public XPushCore(){
    }

    public XPushCore(Context context){
        baseContext = context;
    }


    private void init(){
        if( getBaseContext() != null ) {
            mXpushSession = restoreXpushSession();
            mHostname = getBaseContext().getString(R.string.host_name);
            mAppId = getBaseContext().getString(R.string.app_id);
            mDeviceId = getBaseContext().getString(R.string.device_id);
        }
    }

    public static String getHostname(){
        return mHostname;
    }

    public static String getAppId(){
        return mAppId;
    }

    public static void setBaseContext(Context context){
        baseContext = context;
        XPushCore.getInstance().init();
    }

    public static Context getBaseContext(){
        if( baseContext == null){
            Log.w(TAG, "!!!!! baseContext is null !!!!!");
            baseContext = ApplicationController.getInstance();
        }
        return baseContext;
    }

    public static void setGlobalSocket(Socket socket){
        mGlobalSocket = socket;
        latch.countDown();
    }

    public static void setGlobalSocketConnected(){
        mGlobalSocketConnected.getAndSet(true);
    }

    private Socket getClient() {

        try {
            if( !mGlobalSocketConnected.get() ) {

                // create three threads passing a CountDownLatch
                Thread T1 = new Thread(){
                    public void run() {
                        Log.d(TAG, "--- connect --- ");
                        connect();
                    }
                };

                Thread T2 = new Thread(){
                    public void run() {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        latch.countDown();
                    }
                };

                T1.start();
                T2.start();

                try {
                    latch.await(6, TimeUnit.SECONDS);
                } catch(InterruptedException ie) {
                    ie.printStackTrace();
                }
            }
        } catch (Exception e) {
            // Happens if someone interrupts your thread.
            e.printStackTrace();
        }

        if( mGlobalSocket == null ){
            Log.d(TAG, "null null null");
        }

        return mGlobalSocket;
    }

    public static boolean isGlobalConnected(){
        return mGlobalSocketConnected.get();
    }

    /**
     *
     * Auth start
     *
     *
     */
    public static void register(String id, String password, String name, final CallbackEvent callbackEvent){

        getInstance();

        final Map<String,String> params = new HashMap<String, String>();

        if( name != null ) {

            JSONObject data = new JSONObject();
            try {
                data.put("NM", name);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            params.put("DT", data.toString());
        }

        params.put("A", mAppId);
        params.put("U", id);
        params.put("PW", password);
        params.put("D", mDeviceId);
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        if( null != pref.getString("REGISTERED_NOTIFICATION_ID", null)){
            params.put("N", pref.getString("REGISTERED_NOTIFICATION_ID", null) );
        }
        String url = mHostname+"/user/register";

        StringRequest request = new StringRequest(url, params,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        Log.d(TAG, response.toString());
                        try {
                            if( "ok".equalsIgnoreCase(response.getString("status")) ){
                                callbackEvent.call();
                            } else {
                                if( response.has("message") ){
                                    callbackEvent.call(response.getString("status"), response.getString("message") );
                                } else {
                                    callbackEvent.call(response.getString("status"));
                                }
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        error.printStackTrace();
                        callbackEvent.call( error.getMessage() );
                    }
                }
        );

        RequestQueue queue = Volley.newRequestQueue(getBaseContext());
        queue.add(request);
    }

    /**
     *
     * Auth start
     *
     *
     */
    public static void login(String id, String password, final CallbackEvent callbackEvent){

        getInstance();

        final Map<String,String> params = new HashMap<String, String>();

        params.put("A", mAppId);
        params.put("U", id);
        params.put("PW", password);
        params.put("D", mDeviceId);
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        if( null != pref.getString("REGISTERED_NOTIFICATION_ID", null)){
            params.put("N", pref.getString("REGISTERED_NOTIFICATION_ID", null) );
        }

        String url = mHostname+"/auth";

        LoginRequest request = new LoginRequest(getBaseContext(), url, params,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            if( "ok".equalsIgnoreCase(response.getString("status")) ){
                                callbackEvent.call();
                            } else {
                                if( response.has("message") ){
                                    callbackEvent.call(response.getString("status"), response.getString("message") );
                                } else {
                                    callbackEvent.call(response.getString("status"));
                                }
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        error.printStackTrace();
                        callbackEvent.call( "SERVER-ERROR", error.getMessage() );
                    }
                }
        );

        RequestQueue queue = Volley.newRequestQueue(getBaseContext());
        queue.add(request);
    }

    /**
     *
     * logout
     *
     *
     */
    public static void logout(){
        getInstance();
        mXpushSession = null;
        XPushService.actionStop(baseContext);
        sInstance = null;

        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        SharedPreferences.Editor editor = pref.edit();
        editor.remove("XPUSH_SESSION");
        editor.commit();
    }

    /**
     *
     * Add device start
     *
     *
     */
    public static void addDevice(String id, String password, final CallbackEvent callbackEvent){

        getInstance();

        final Map<String,String> params = new HashMap<String, String>();

        params.put("A", mAppId);
        params.put("U", id);
        params.put("PW", password);
        params.put("D", mDeviceId);

        String url = mHostname+"/device/add";

        LoginRequest request = new LoginRequest(getBaseContext(), url, params,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            if( "ok".equalsIgnoreCase(response.getString("status")) ){
                                callbackEvent.call();
                            } else {
                                if( response.has("message") ){
                                    callbackEvent.call(response.getString("status"), response.getString("message") );
                                } else {
                                    callbackEvent.call(response.getString("status"));
                                }
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        error.printStackTrace();
                        callbackEvent.call("SERVER-ERROR", error.getMessage());
                    }
                }
        );

        RequestQueue queue = Volley.newRequestQueue(getBaseContext());
        queue.add(request);
    }

    public static void updateToken(final CallbackEvent callbackEvent){
        updateUser(null, callbackEvent);
    }

    public static void updateUser(final JSONObject mJsonUserData, final CallbackEvent callbackEvent){
        if( mXpushSession == null ){
            return;
        }

        getInstance();

        final Map<String,String> params = new HashMap<String, String>();

        params.put("A", mAppId);
        params.put("U", mXpushSession.getId());
        params.put("PW", mXpushSession.getPassword());
        params.put("D", mXpushSession.getDeviceId());

        if( mJsonUserData != null ) {
            params.put("DT", mJsonUserData.toString());
        }

        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        if( null != pref.getString("REGISTERED_NOTIFICATION_ID", null)){
            params.put("N", pref.getString("REGISTERED_NOTIFICATION_ID", null) );
        }

        String url = getBaseContext().getString(R.string.host_name)+"/user/update";

        StringRequest request = new StringRequest(url, params,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        Log.d(TAG, "Update user success ======================");
                        Log.d(TAG, response.toString());
                        try {
                            if( "ok".equalsIgnoreCase(response.getString("status")) ){
                                Log.d(TAG, response.getString("status"));

                                if( mJsonUserData != null ) {
                                    if (mJsonUserData.has("I")) {
                                        mXpushSession.setImage(mJsonUserData.getString("I"));
                                    }
                                    if (mJsonUserData.has("NM")) {
                                        mXpushSession.setName(mJsonUserData.getString("NM"));
                                    }
                                    if (mJsonUserData.has("MG")) {
                                        mXpushSession.setMessage(mJsonUserData.getString("MG"));
                                    }
                                }

                                if( params.containsKey("N")) {
                                    mXpushSession.setNotiId(params.get("N"));
                                }

                                // SessionData Update
                                SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(baseContext);
                                SharedPreferences.Editor editor = pref.edit();
                                editor.putString("XPUSH_SESSION", mXpushSession.toJSON().toString());
                                editor.commit();

                                if( mJsonUserData != null ) {
                                    callbackEvent.call(mJsonUserData);
                                } else {
                                    callbackEvent.call(response);
                                }
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d(TAG, "Update user error ======================");
                        error.printStackTrace();
                    }
                }
        );

        RequestQueue queue = Volley.newRequestQueue(getBaseContext());
        queue.add(request);
    }

    /**
     *
     * Session Start
     *
     */
    public static XPushSession restoreXpushSession(){
        getInstance();
        if( mXpushSession == null ){
            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
            final String sessionStr = pref.getString("XPUSH_SESSION", "");
            if( !"".equals( sessionStr ) ){
                try {
                    mXpushSession = new XPushSession( new JSONObject( sessionStr ) );
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }

        return mXpushSession;
    }

    public static boolean isLogined(){
        getInstance();
        if( restoreXpushSession() != null ){
            return true;
        } else {
            return false;
        }
    }

    public static XPushSession getXpushSession(){
        getInstance();
        if( mXpushSession == null ){
            restoreXpushSession();
        }

        return mXpushSession;
    }

    /**
     *
     * Channel start
     *
     *
     */

    public static void createChannel(final XPushChannel xpushChannel, final CallbackEvent callbackEvent){

        JSONArray userArray = new JSONArray();
        for( String userId : xpushChannel.getUsers() ){
            userArray.put(userId);
        }
        final String cid = xpushChannel.getId() != null ? xpushChannel.getId() : XPushUtils.generateChannelId(xpushChannel.getUsers());
        xpushChannel.setId( cid );

        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("C", cid);
            jsonObject.put("U", userArray);

        } catch (JSONException e) {
            e.printStackTrace();
        }

        if ( mGlobalSocket == null || !mGlobalSocket.connected() ){
            Log.d(TAG, "Not connected");
            return;
        }

        getInstance().getClient().emit("channel.create", jsonObject, new Ack() {
            @Override
            public void call(Object... args) {
                JSONObject response = (JSONObject) args[0];

                if (response.has("status")) {
                    try {
                        Log.d(TAG, response.getString("status"));
                        if ("ok".equalsIgnoreCase(response.getString("status")) || "WARN-EXISTED".equals(response.getString("status"))
                                // duplicate
                                || ("ERR-INTERNAL".equals(response.getString("status"))) && response.get("message").toString().indexOf("E11000") > -1) {

                            getChannel(cid, callbackEvent);
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }


    public static void getChannel(final String channelId, final CallbackEvent callbackEvent){
        getChannel(getBaseContext(), channelId, callbackEvent);
    }

    public static void getChannel(Context context, final String channelId, final CallbackEvent callbackEvent){
        getInstance();
        String url = null;
        try {
            url = mHostname + "/node/"+ getInstance().getAppId()+"/"+ URLEncoder.encode(channelId, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        StringRequest request = new StringRequest(url,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            ChannelCore result = null;
                            if( "ok".equalsIgnoreCase(response.getString("status")) ){
                                JSONObject serverInfo = response.getJSONObject("result").getJSONObject("server");
                                result = new ChannelCore(channelId, serverInfo.getString("url"), serverInfo.getString("name") );
                            }

                            callbackEvent.call( result );
                        } catch (JSONException e) {
                            e.printStackTrace();
                            callbackEvent.call();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        error.printStackTrace();
                        callbackEvent.call(error.getMessage());
                    }
                }
        );

        RequestQueue queue = Volley.newRequestQueue(context);
        queue.add(request);
    }

    public static void storeChannel(XPushChannel channel){
        getInstance();
        ContentValues values = new ContentValues();
        values.put(ChannelTable.KEY_ID, channel.getId());
        values.put(ChannelTable.KEY_NAME, channel.getName());
        values.put(ChannelTable.KEY_IMAGE, channel.getImage());
        values.put(ChannelTable.KEY_MESSAGE, channel.getMessage());
        values.put(ChannelTable.KEY_UPDATED, System.currentTimeMillis());

        values.put(XpushContentProvider.SQL_INSERT_OR_REPLACE, true);
        getBaseContext().getContentResolver().insert(XpushContentProvider.CHANNEL_CONTENT_URI, values);
    }

    /**
     *
     * Group Start
     *
     */
    public static void getFriends(final CallbackEvent callbackEvent) {


        JSONObject jsonObject = new JSONObject();

        try {
            jsonObject.put("GR", mXpushSession.getId());
        } catch (JSONException e) {
            e.printStackTrace();
        }

        try {
            getInstance().getClient().emit("group.list", jsonObject, new Ack() {
                @Override
                public void call(Object... args) {
                    JSONObject response = (JSONObject) args[0];

                    Log.d(TAG, response.toString());
                    if (response.has("result")) {
                        try {
                            JSONArray result = (JSONArray) response.getJSONArray("result");
                            callbackEvent.call(result);

                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
        } catch ( Exception ee ){
            ee.printStackTrace();
        }
    }

    public static void storeFriends(final Context mContext, final JSONArray result) {
        getInstance();
        try {
            List<ContentValues> valuesToInsert = new ArrayList<ContentValues>();

            for (int inx = 0; inx < result.length(); inx++) {
                JSONObject json = (JSONObject) result.get(inx);

                ContentValues contentValues = new ContentValues();
                contentValues.put(UserTable.KEY_ID, json.getString("U"));

                if (json.has("DT") && !json.isNull("DT")) {
                    Object obj = json.get("DT");
                    JSONObject data = null;
                    if (obj instanceof JSONObject) {
                        data = (JSONObject) obj;
                    } else if (obj instanceof String) {
                        data = new JSONObject((String) obj);
                    }

                    if (data.has("NM")) {
                        contentValues.put(UserTable.KEY_NAME, data.getString("NM"));
                    }
                    if (data.has("MG")) {
                        contentValues.put(UserTable.KEY_MESSAGE, data.getString("MG"));
                    }
                    if (data.has("I")) {
                        contentValues.put(UserTable.KEY_IMAGE, data.getString("I"));
                    }
                } else {
                    contentValues.put(UserTable.KEY_NAME, json.getString("U"));
                }

                contentValues.put(XpushContentProvider.SQL_INSERT_OR_REPLACE, true);
                valuesToInsert.add(contentValues);
            }


            mContext.getContentResolver().bulkInsert(XpushContentProvider.USER_CONTENT_URI, valuesToInsert.toArray(new ContentValues[0]));

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public static void addFriend(final Context mContext, final XPushUser user, final CallbackEvent callbackEvent) {

        JSONObject jsonObject = new JSONObject();
        JSONArray array = new JSONArray();

        try {
            array.put( user.getId() );

            jsonObject.put("GR", XPushCore.getXpushSession().getId()  );
            jsonObject.put("U", array);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        getInstance().getClient().emit("group.add", jsonObject, new Ack() {
            @Override
            public void call(Object... args) {
                JSONObject response = (JSONObject) args[0];
                Log.d(TAG, response.toString());
                try {
                    if ("ok".equalsIgnoreCase(response.getString("status"))) {

                        ContentValues contentValues = new ContentValues();
                        contentValues.put(UserTable.KEY_ID, user.getId());
                        contentValues.put(UserTable.KEY_NAME, user.getName());
                        contentValues.put(UserTable.KEY_MESSAGE, user.getMessage());
                        contentValues.put(UserTable.KEY_IMAGE, user.getImage());

                        contentValues.put(XpushContentProvider.SQL_INSERT_OR_REPLACE, true);
                        mContext.getContentResolver().insert(XpushContentProvider.USER_CONTENT_URI, contentValues);

                        callbackEvent.call();
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public static void searchUser(final Context context, String searchKey, final CallbackEvent callbackEvent){
        searchUser(context, searchKey, 1, 50, callbackEvent);
    }

    public static void searchUser(final Context context, String searchKey, int pageNum, int pageSize, final CallbackEvent callbackEvent){

        getInstance();

        JSONObject options = new JSONObject();

        try {
            options.put("pageNum", pageNum);
            options.put("pageSize", pageSize);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        final Map<String,String> params = new HashMap<String, String>();
        params.put("A", mAppId);
        params.put("K", searchKey);
        params.put("option", options.toString());

        String url = mHostname+"/user/search";

        StringRequest request = new StringRequest(url, params,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            Log.d(TAG, "====== search user response ====== " + response.toString() );
                            if( "ok".equalsIgnoreCase(response.getString("status")) ){
                                JSONArray result = (JSONArray) response.getJSONObject("result").getJSONArray("users");

                                ArrayList<XPushUser> users = new ArrayList<XPushUser>();

                                for (int inx = 0; inx < result.length(); inx++) {
                                    JSONObject json = (JSONObject) result.get(inx);
                                    Log.d(TAG, json.toString());

                                    XPushUser xpushUser = new XPushUser();

                                    xpushUser.setId(json.getString("U"));

                                    if (json.has("DT") && !json.isNull("DT")) {
                                        Object obj = json.get("DT");
                                        JSONObject data = null;
                                        if (obj instanceof JSONObject) {
                                            data = (JSONObject) obj;
                                        } else if (obj instanceof String) {
                                            data = new JSONObject((String) obj);
                                        }

                                        if (data.has("NM")) {
                                            xpushUser.setName(data.getString("NM"));
                                        } else {
                                            xpushUser.setName(json.getString("U"));
                                        }
                                        if (data.has("MG")) {
                                            xpushUser.setMessage(data.getString("MG"));
                                        }
                                        if (data.has("I")) {
                                            xpushUser.setImage(data.getString("I"));
                                        }
                                    } else {
                                        xpushUser.setName(json.getString("U"));
                                    }

                                    users.add(xpushUser);
                                }

                                callbackEvent.call(users);
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        error.printStackTrace();
                        callbackEvent.call(error.getMessage());
                    }
                }
        );

        RequestQueue queue = Volley.newRequestQueue(context);
        queue.add(request);
    }

    public static String[] uploadImage(Uri uri){

        getInstance();

        String[] results = new String[3];

        String downloadUrl = null;
        String url = mXpushSession.getServerUrl()+"/upload";
        JSONObject userData = new JSONObject();

        try {
            userData.put( "U", mXpushSession.getId() );
            userData.put( "D", mDeviceId );
        } catch (JSONException e) {
            e.printStackTrace();
        }

        String realPath = ContentUtils.getRealPath(getBaseContext(), uri);
        File aFile = new File(realPath);

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(realPath, options);

        int imageWidth = options.outWidth;
        int imageHeight = options.outHeight;

        RequestBody requestBody = new MultipartBuilder()
                .type(MultipartBuilder.FORM)
                .addFormDataPart("file", aFile.getName(), RequestBody.create(Constants.MEDIA_TYPE_PNG, aFile)).build();


        String appId = XPushCore.getAppId();

        Request request = new Request.Builder()
                .url(url)
                .addHeader("XP-A", appId )
                .addHeader("XP-C", getXpushSession().getId() +"^"+ appId)
                .addHeader("XP-U", userData.toString() )
                .addHeader("XP-FU-org",  aFile.getName())
                .addHeader("XP-FU-nm", aFile.getName().substring(0, aFile.getName().lastIndexOf(".") ) )
                .addHeader("XP-FU-tp", "image")
                .post(requestBody)
                .build();

        OkHttpClient client = new OkHttpClient();

        com.squareup.okhttp.Response response = null;
        try {
            response = client.newCall(request).execute();
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }

            JSONObject res = new JSONObject( response.body().string() );
            Log.d(TAG,  res.toString() );

            if( "ok".equals(res.getString("status")) ) {
                JSONObject result = res.getJSONObject("result");

                String channel = result.getString("channel");
                String tname = result.getString("name");

                downloadUrl = mXpushSession.getServerUrl() + "/download/" + appId + "/" + channel + "/" + mXpushSession.getId() + "/" + tname;

                Log.d(TAG, downloadUrl );
            }


            results[0] = downloadUrl;
            results[1] = String.valueOf(imageWidth);
            results[2] = String.valueOf(imageHeight);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return results;
    }

    public synchronized void connect() {

        if( getOnlineType() == NETWORK_NOT_AVAILABLE ){
            Log.d(TAG, "Network is not available");
            return;
        }

        if( mConnecting.get() ){
            Log.d(TAG, "Tring connect");
            return;
        }

        mConnecting.set(true);

        // fetch the device ID from the preferences.
        String appId = getBaseContext().getString(R.string.app_id);
        String url = mXpushSession.getServerUrl() + "/global";

        IO.Options opts = new IO.Options();
        opts.forceNew = true;
        opts.reconnectionAttempts = 20;
        try {
            opts.query = "A="+appId+"&U="+ URLEncoder.encode(mXpushSession.getId(), "UTF-8")+"&TK="+mXpushSession.getToken()+"&D="+mXpushSession.getDeviceId();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        IO.Options mOpts = opts;
        Log.i(TAG, "Connecting with URL in XPushCore : " + url);
        try {
            mGlobalSocket = IO.socket(url, mOpts);
            HashMap<String, Emitter.Listener> events = new HashMap<>();

            events.put(Socket.EVENT_CONNECT, onConnectSuccess);
            events.put(Socket.EVENT_CONNECT_ERROR, onConnectError);
            events.put("_event", onNewMessage);

            if (events != null) {
                for (String eventName : events.keySet()) {
                    mGlobalSocket.on(eventName, events.get(eventName));
                }
            }

            mEvents = events;
        } catch (Exception e) {
            e.printStackTrace();
        }

        mGlobalSocket.connect();
    }

    private Emitter.Listener onNewMessage = new Emitter.Listener() {
        @Override
        public void call(final Object... args) {
            JSONObject json = (JSONObject) args[0];
            Log.d(TAG, "on GlobalMesssage" );
            XPushService.handleMessage(baseContext, json);
        }
    };

    private Emitter.Listener onConnectSuccess = new Emitter.Listener() {
        @Override
        public void call(Object... args) {
            Log.d(TAG, "connect sucesss");
            mConnecting.set(false);
            setGlobalSocketConnected();
        }
    };

    private Emitter.Listener onConnectError = new Emitter.Listener() {
        @Override
        public void call(Object... args) {

            if( args[0] instanceof SocketIOException) {
                SocketIOException e = (SocketIOException) args[0];
                Log.d(TAG, e.getMessage());
            } else if ( args[0] instanceof EngineIOException){
                EngineIOException e = (EngineIOException) args[0];
                Log.d(TAG, e.getMessage());
            }
            mConnecting.set(false);
        }
    };

    private int getOnlineType() {
        try {
            ConnectivityManager conMan = (ConnectivityManager) baseContext.getSystemService(Context.CONNECTIVITY_SERVICE);

            NetworkInfo.State wifi = conMan.getNetworkInfo(1).getState(); // wifi
            if (wifi == NetworkInfo.State.CONNECTED || wifi == NetworkInfo.State.CONNECTING)
                return NETWORK_WIFI;

            NetworkInfo.State mobile = conMan.getNetworkInfo(0).getState();
            if (mobile == NetworkInfo.State.CONNECTED || mobile == NetworkInfo.State.CONNECTING)
                return NETWORK_MOBILE;

        } catch (NullPointerException e) {
            return NETWORK_NOT_AVAILABLE;
        }

        return NETWORK_NOT_AVAILABLE;
    }

    public void disconnect(){
        for (String eventName : mEvents.keySet()) {
            this.off(eventName);
        }
        Log.d(TAG, "=== disconnect global socket ===");
        mGlobalSocket.disconnect();
    }

    public Socket getGlobalSocket(){
        return mGlobalSocket;
    }

    public boolean isConnecting(){
        return mConnecting.get();
    }

    public void on(String event, Emitter.Listener eventListener) {
        if( !mEvents.containsKey( event) ){
            mGlobalSocket.on(event, eventListener);
        }
    }

    public void off(String event) {
        mGlobalSocket.off(event);
    }
} public class MessageDataLoader extends AbstractDataLoader<List<XPushMessage>> {
    private DataSource<XPushMessage> mDataSource;
    private String mSelection;
    private String[] mSelectionArgs;
    private String mGroupBy;
    private String mHaving;
    private String mSortOrder;

    public void setSelection( String selection ){
        this.mSelection = selection;
    }

    public void setSortOrder( String sortOrder ){
        this.mSortOrder = sortOrder;
    }

    public MessageDataLoader(Context context, DataSource dataSource, String selection, String[] selectionArgs,
                                String groupBy, String having, String sortOrder) {
        super(context);
        mDataSource = dataSource;
        mSelection = selection;
        mSelectionArgs = selectionArgs;
        mGroupBy = groupBy;
        mHaving = having;
        mSortOrder = sortOrder;
    }

    @Override
    protected List<XPushMessage> buildList() {
        List<XPushMessage> testList = mDataSource.read(mSelection, mSelectionArgs, mGroupBy, mHaving,
                mSortOrder);
        return testList;
    }

    public void insert(XPushMessage entity) {
        new InsertTask(this).execute(entity);
    }

    public void update(XPushMessage entity) {
        new UpdateTask(this).execute(entity);
    }

    public void delete(XPushMessage entity) {
        new DeleteTask(this).execute(entity);
    }

    private class InsertTask extends ContentChangingTask<XPushMessage, Void, Void> {
        InsertTask(MessageDataLoader loader) {
            super(loader);
        }

        @Override
        protected Void doInBackground(XPushMessage... params) {
            mDataSource.insert(params[0]);
            return (null);
        }
    }

    private class UpdateTask extends ContentChangingTask<XPushMessage, Void, Void> {
        UpdateTask(MessageDataLoader loader) {
            super(loader);
        }

        @Override
        protected Void doInBackground(XPushMessage... params) {
            mDataSource.update(params[0]);
            return (null);
        }
    }

    private class DeleteTask extends ContentChangingTask<XPushMessage, Void, Void> {
        DeleteTask(MessageDataLoader loader) {
            super(loader);
        }

        @Override
        protected Void doInBackground(XPushMessage... params) {
            mDataSource.delete(params[0]);
            return (null);
        }
    }
} public class XPushChannel {

    public static final String CHANNEL_BUNDLE = "CHANNEL_BUNDLE";

    public static final String ID = "id";
    public static final String NAME = "name";
    public static final String USERS = "users";
    public static final String USER_NAMES = "user_names";
    public static final String IMAGE = "image";
    public static final String COUNT = "count";
    public static final String MESSAGE = "message";
    public static final String MESSAGE_TYPE = "message_type";
    public static final String UPDATED = "updated";

    public String rowId;
    public String id;
    public String name;
    public ArrayList<String> users;

    public ArrayList<String> userNames;
    public String image;
    public int count;
    public String message;
    public int messageType;
    public long updated;

    public String getRowId() {
        return rowId;
    }

    public void setRowId(String rowId) {
        this.rowId = rowId;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ArrayList<String> getUsers() {
        return users;
    }

    public void setUsers(ArrayList<String> users) {
        this.users = users;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public long getUpdated() {
        return updated;
    }

    public void setUpdated(long updated) {
        this.updated = updated;
    }

    public int getMessageType(){
        return messageType;
    }

    public void setMessageType(int messageType){
        this.messageType = messageType;
    }

    public ArrayList<String> getUserNames() {
        return userNames;
    }

    public void setUserNames(ArrayList<String> userNames) {
        this.userNames = userNames;
    }

    public XPushChannel(){
    }

    public XPushChannel(Cursor cursor){
        this.rowId= cursor.getString(cursor.getColumnIndexOrThrow(ChannelTable.KEY_ROWID));
        this.id= cursor.getString(cursor.getColumnIndexOrThrow(ChannelTable.KEY_ID));
        this.name= cursor.getString(cursor.getColumnIndexOrThrow(ChannelTable.KEY_NAME));
        this.image= cursor.getString(cursor.getColumnIndexOrThrow(ChannelTable.KEY_IMAGE));
        this.count= cursor.getInt(cursor.getColumnIndexOrThrow(ChannelTable.KEY_COUNT));
        this.message= cursor.getString(cursor.getColumnIndexOrThrow(ChannelTable.KEY_MESSAGE));
        this.updated= cursor.getLong(cursor.getColumnIndexOrThrow(ChannelTable.KEY_UPDATED));
        this.messageType = cursor.getInt(cursor.getColumnIndexOrThrow(ChannelTable.KEY_MESSAGE_TYPE));
        String usersStr = cursor.getString(cursor.getColumnIndexOrThrow(ChannelTable.KEY_USERS));

        if( usersStr != null && usersStr.indexOf("@!@") > 0 ) {
            this.users = new ArrayList<String>(Arrays.asList(usersStr.split("@!@")));
        } else if( this.id.indexOf("@!@") > -1 ){
            usersStr = this.id.substring( 0, this.id.lastIndexOf("^") );
            this.users = new ArrayList<String>(Arrays.asList(usersStr.split("@!@")));
        }
    }

    public XPushChannel(Bundle bundle){
        this.id= bundle.getString(ID);
        this.name= bundle.getString(NAME);
        this.users= bundle.getStringArrayList(USERS);
        this.userNames= bundle.getStringArrayList(USER_NAMES);
        this.image= bundle.getString(IMAGE);
        this.count= bundle.getInt(COUNT);
        this.message= bundle.getString(MESSAGE);
        this.messageType= bundle.getInt(MESSAGE_TYPE);
        this.updated= bundle.getLong(UPDATED);
    }

    public Bundle toBundle() {
        Bundle b = new Bundle();
        b.putString(ID, this.id);
        b.putString(NAME, this.name);
        b.putStringArrayList(USERS, this.users);
        b.putStringArrayList(USER_NAMES, this.userNames);

        b.putString(IMAGE, this.image);
        b.putInt(COUNT, this.count);
        b.putString(MESSAGE, this.message);
        b.putInt(MESSAGE_TYPE, this.messageType);
        b.putLong(UPDATED, this.updated);

        return b;
    }

    @Override
    public String toString(){
        return "XPushChannel{" +
                "rowId='" + rowId + '\'' +
                ", id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", users='" + users + '\'' +
                ", image='" + image + '\'' +
                ", count='" + count + '\'' +
                ", message='" + message + '\'' +
                ", messageType='" + messageType + '\'' +
                ", updated='" + updated + '\'' +
                '}';
    }
} public class XPushMessage {

    public static final int TYPE_SEND_MESSAGE = 0;
    public static final int TYPE_RECEIVE_MESSAGE = 1;

    public static final int TYPE_INVITE = 2;
    public static final int TYPE_LEAVE = 3;

    public static final int TYPE_IMAGE = 4;
    public static final int TYPE_SEND_IMAGE = 5;
    public static final int TYPE_RECEIVE_IMAGE = 6;

    private String rowId;
    private String id;
    private String channel;
    private String senderId;
    private String senderName;
    private String image;
    private String count;
    private String message;
    private int type;
    private JSONObject metadata;
    private long updated;
    private ArrayList<String> users;

    public String getRowId() {
        return rowId;
    }

    public void setRowId(String rowId) {
        this.rowId = rowId;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getChannel() {
        return channel;
    }

    public void setChannel(String channel) {
        this.channel = channel;
    }

    public String getSenderId() {
        return senderId;
    }

    public void setSenderId(String senderId) {
        this.senderId = senderId;
    }

    public String getSenderName() {
        return senderName;
    }

    public void setSenderName(String senderName) {
        this.senderName = senderName;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getCount() {
        return count;
    }

    public void setCount(String count) {
        this.count = count;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public long getUpdated() {
        return updated;
    }

    public void setUpdated(long updated) {
        this.updated = updated;
    }

    public ArrayList<String> getUsers(){
        return this.users;
    }

    public void setUsers(ArrayList<String> users){
        this.users = users;
    }

    public JSONObject getMetadata() {
        return metadata;
    }

    public void setMetadata(JSONObject metadata) {
        this.metadata = metadata;
    }

    public XPushMessage(){
    }

    public XPushMessage(JSONObject data) {

        JSONObject uo = null;
        try {
            if( data.has("UO") ) {
                uo = data.getJSONObject("UO");

                if( uo.has("U") ){
                    this.senderId = uo.getString("U");
                }

                if( uo.has("NM") ){
                    this.senderName = uo.getString("NM");
                }

                if( uo.has("I") ) {
                    this.image = uo.getString("I");
                }
            }

            // Message Type
            if( data.has("TP") ){
                if( "IN".equals(data.getString("TP")) ) {
                    this.type = TYPE_INVITE;
                } else if( "OUT".equals(data.getString("TP")) ) {
                    this.type = TYPE_LEAVE;
                } else if( "IM".equals(data.getString("TP")) ) {
                    this.type = TYPE_IMAGE;
                }
            }

            // Message Metadata
            if( data.has("MD") ){
                this.metadata = data.getJSONObject("MD");
            }

            this.channel = data.getString("C");

            if( data.has("US") ){
                String usersStr = data.getString("US");
                this.users = new ArrayList<String>(Arrays.asList(usersStr.split("@!@")));
            }  else if( this.channel != null && this.channel.indexOf("@!@") > -1 && this.channel.lastIndexOf("^") > 0 ){
                String usersStr = this.channel.substring( 0, this.channel.lastIndexOf("^") );
                this.users = new ArrayList<String>(Arrays.asList(usersStr.split("@!@")));
            }

            this.message = URLDecoder.decode( data.getString("MG"), "UTF-8");
            this.updated = data.getLong("TS");

            this.id = channel +"_" + updated;

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public XPushMessage(Cursor cursor){
        this.rowId= cursor.getString(cursor.getColumnIndexOrThrow(MessageTable.KEY_MESSAGE));
        this.id= cursor.getString(cursor.getColumnIndexOrThrow(MessageTable.KEY_ID));
        this.senderName= cursor.getString(cursor.getColumnIndexOrThrow(MessageTable.KEY_SENDER));
        this.image= cursor.getString(cursor.getColumnIndexOrThrow(MessageTable.KEY_IMAGE));
        this.message= cursor.getString(cursor.getColumnIndexOrThrow(MessageTable.KEY_MESSAGE));
        this.type= cursor.getInt(cursor.getColumnIndexOrThrow(MessageTable.KEY_TYPE));
        this.updated= cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.KEY_UPDATED));

        String metadata = cursor.getString(cursor.getColumnIndexOrThrow(MessageTable.KEY_METADATA));
        if( metadata != null ){
            try {
                this.metadata = new JSONObject(metadata);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public String toString(){
        return "XPushMessage{" +
                "rowId='" + rowId + '\'' +
                ", id='" + id + '\'' +
                ", senderId='" + senderId + '\'' +
                ", image='" + image + '\'' +
                ", message='" + message + '\'' +
                ", type='" + type + '\'' +
                ", updated='" + updated + '\'' +
                '}';
    }
} public class XPushSession {

    public static final String ID = "U";
    public static final String PASSWORD = "PW";
    public static final String DEVICE_ID = "D";
    public static final String TOKEN = "TK";
    public static final String NOTI_ID = "N";
    public static final String SERVER_NAME = "SERVER_NAME";
    public static final String SERVER_URL = "SERVER_URL";
    public static final String IMAGE = "I";
    public static final String NAME = "NM";
    public static final String MESSAGE = "MG";
    public static final String EMAIL = "EM";

    private String id;
    private String password;
    private String deviceId;
    private String token;
    private String notiId;
    private String serverName;
    private String image;
    private String serverUrl;
    private String name;
    private String message;
    private String email;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getNotiId() {
        return notiId;
    }

    public void setNotiId(String notiId) {
        this.notiId = notiId;
    }

    public String getServerName() {
        return serverName;
    }

    public void setServerName(String serverName) {
        this.serverName = serverName;
    }

    public String getServerUrl() {
        return serverUrl;
    }

    public void setServerUrl(String serverUrl) {
        this.serverUrl = serverUrl;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public XPushSession(){
    }

    public XPushSession(Bundle bundle){
        this.id= bundle.getString(ID);
        this.password= bundle.getString(PASSWORD);
        this.deviceId= bundle.getString(DEVICE_ID);
        this.token= bundle.getString(TOKEN);
        this.notiId= bundle.getString(NOTI_ID);
        this.serverName= bundle.getString(SERVER_NAME);
        this.serverUrl= bundle.getString(SERVER_URL);
        this.image = bundle.getString(IMAGE);
        this.name = bundle.getString(NAME);
        this.message = bundle.getString(MESSAGE);
        this.email = bundle.getString(EMAIL);
    }

    public XPushSession(JSONObject object){
        try {
            if( object.has(ID)) {
                this.id = object.getString(ID);
            }
            if( object.has(PASSWORD)) {
                this.password = object.getString(PASSWORD);
            }
            if( object.has(DEVICE_ID)) {
                this.deviceId = object.getString(DEVICE_ID);
            }
            if( object.has(TOKEN)) {
                this.token = object.getString(TOKEN);
            }
            if( object.has(NOTI_ID)) {
                this.notiId = object.getString(NOTI_ID);
            }
            if( object.has(SERVER_NAME)) {
                this.serverName = object.getString(SERVER_NAME);
            }
            if( object.has(SERVER_URL)) {
                this.serverUrl = object.getString(SERVER_URL);
            }

            if( object.has(IMAGE)) {
                this.image = object.getString(IMAGE);
            }

            if( object.has(NAME)) {
                this.name = object.getString(NAME);
            }

            if( object.has(MESSAGE)) {
                this.message = object.getString(MESSAGE);
            }

            if( object.has(EMAIL)) {
                this.email = object.getString(EMAIL);
            }

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public JSONObject toJSON() {
        JSONObject j = new JSONObject();
        try {
            j.put(ID, this.id);
            j.put(PASSWORD, this.password);
            j.put(DEVICE_ID, this.deviceId);
            j.put(TOKEN, this.token);
            j.put(NOTI_ID, this.notiId);
            j.put(SERVER_NAME, this.serverName);
            j.put(SERVER_URL, this.serverUrl);
            j.put(IMAGE, this.image);
            j.put(NAME, this.name);
            j.put(MESSAGE, this.message);
            j.put(EMAIL, this.email);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return j;
    }

    public JSONObject getUserData(){
        JSONObject userData = new JSONObject();
        try {
            userData.put("NM", name);
            userData.put("I", image);
            userData.put("MG", message);
            userData.put("EM", email);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return userData;
    }
} public class XPushUser implements Parcelable {

    public static final String USER_BUNDLE = "USER_BUNDLE";

    public static final String ID = "id";
    public static final String NAME = "name";
    public static final String IMAGE = "image";
    public static final String MESSAGE = "message";
    public static final String TYPE = "type";
    public static final String UPDATED = "updated";

    public String rowId;
    public String id;
    public String name;
    public String image;
    public String message;
    public int type;
    public long updated;

    public String getRowId() {
        return rowId;
    }

    public void setRowId(String rowId) {
        this.rowId = rowId;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public long getUpdated() {
        return updated;
    }

    public void setUpdated(long updated) {
        this.updated = updated;
    }

    public XPushUser(){
    }

    public XPushUser(Parcel in) {
        readFromParcel(in);
    }

    public XPushUser(Cursor cursor){
        this.rowId= cursor.getString(cursor.getColumnIndexOrThrow(UserTable.KEY_ROWID));
        this.id= cursor.getString(cursor.getColumnIndexOrThrow(UserTable.KEY_ID));
        this.name= cursor.getString(cursor.getColumnIndexOrThrow(UserTable.KEY_NAME));
        this.image= cursor.getString(cursor.getColumnIndexOrThrow(UserTable.KEY_IMAGE));
        this.message= cursor.getString(cursor.getColumnIndexOrThrow(UserTable.KEY_MESSAGE));
        this.type = cursor.getInt(cursor.getColumnIndexOrThrow(UserTable.KEY_TYPE));
        this.updated= cursor.getLong(cursor.getColumnIndexOrThrow(UserTable.KEY_UPDATED));
    }

    public XPushUser(Bundle bundle){
        this.id= bundle.getString(ID);
        this.name= bundle.getString(NAME);
        this.image= bundle.getString(IMAGE);
        this.message= bundle.getString(MESSAGE);
        this.type= bundle.getInt(TYPE);
        this.updated= bundle.getLong(UPDATED);
    }

    public XPushUser(JSONObject data){

        try{
            if( data.has("DT") ) {

                JSONObject dt = data.getJSONObject("DT");

                if( dt.has("NM") ) {
                    this.name = dt.getString("NM");
                }

                if( dt.has("I") ) {
                    this.image = dt.getString("I");
                }
            }

            if( data.has("U") ) {
                this.id = data.getString("U");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Bundle toBundle() {
        Bundle b = new Bundle();
        b.putString(ID, this.id);
        b.putString(NAME, this.name);
        b.putString(IMAGE, this.image);
        b.putString(MESSAGE, this.message);
        b.putInt(TYPE, this.type);
        b.putLong(UPDATED, this.updated);

        return b;
    }

    @Override
    public String toString(){
        return "XPushUser{" +
                "rowId='" + rowId + '\'' +
                ", id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", image='" + image + '\'' +
                ", message='" + message + '\'' +
                ", type='" + type + '\'' +
                ", updated='" + updated + '\'' +
                '}';
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(rowId);
        dest.writeString(id);
        dest.writeString(name);
        dest.writeString(image);
        dest.writeString(message);
        dest.writeInt(type);
        dest.writeLong(updated);
    }

    public void readFromParcel(Parcel in) {
        rowId = in.readString();
        id = in.readString();
        name = in.readString();
        image = in.readString();
        message = in.readString();
        type = in.readInt();
        updated = in.readLong();
    }

    @SuppressWarnings("rawtypes")
    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {

        @Override
        public XPushUser createFromParcel(Parcel in) {
            return new XPushUser(in);
        }

        @Override
        public XPushUser[] newArray(int size) {
            return new XPushUser[size];
        }
    };
} public class ChannelTable {

    public static final String KEY_ROWID = "_id";
    public static final String KEY_ID = "id";
    public static final String KEY_NAME = "name";
    public static final String KEY_USERS = "users";
    public static final String KEY_IMAGE = "image";
    public static final String KEY_COUNT = "count";
    public static final String KEY_MESSAGE = "message";
    public static final String KEY_MESSAGE_TYPE = "message_type";
    public static final String KEY_UPDATED = "updated";
    public static String SQLITE_TABLE;

    public static final String[] ALL_PROJECTION = {
        KEY_ROWID,
        KEY_ID,
        KEY_NAME,
        KEY_USERS,
        KEY_IMAGE,
        KEY_COUNT,
        KEY_MESSAGE,
        KEY_MESSAGE_TYPE,
        KEY_UPDATED
    };

    public static void onCreate(SQLiteDatabase db, String tableName) {
        SQLITE_TABLE = tableName;

        db.execSQL("DROP TABLE IF EXISTS " + tableName);

        String DATABASE_CREATE =
                "CREATE TABLE if not exists " + tableName + " (" +
                        KEY_ROWID + " integer PRIMARY KEY autoincrement," +
                        KEY_ID + " unique ," +
                        KEY_NAME + "," +
                        KEY_USERS + "," +
                        KEY_IMAGE + "," +
                        KEY_COUNT + " integer ," +
                        KEY_MESSAGE + "," +
                        KEY_MESSAGE_TYPE + " integer ,"+
                        KEY_UPDATED + " integer );";

        db.execSQL(DATABASE_CREATE);
    }

    public static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion, String tableName) {
        SQLITE_TABLE = tableName;

        db.execSQL("DROP TABLE IF EXISTS " + tableName);
        onCreate(db, tableName);
    }
} public class DBHelper extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "xpush.db";
    private static final int DATABASE_VERSION = 2;
    private String CHANNEL_TABLE_NAME;
    private String MESSAGE_TABLE_NAME;
    private String USER_TABLE_NAME;

    public DBHelper(Context context) {
        super(context, DATABASE_NAME, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
        CHANNEL_TABLE_NAME = context.getString(R.string.channel_table_name);
        MESSAGE_TABLE_NAME = context.getString(R.string.message_table_name);
        USER_TABLE_NAME = context.getString(R.string.user_table_name);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        MessageTable.onCreate(db, MESSAGE_TABLE_NAME);
        ChannelTable.onCreate(db, CHANNEL_TABLE_NAME);
        UserTable.onCreate(db,USER_TABLE_NAME);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        MessageTable.onUpgrade(db, oldVersion, newVersion, MESSAGE_TABLE_NAME);
        ChannelTable.onUpgrade(db, oldVersion, newVersion, CHANNEL_TABLE_NAME);
        UserTable.onUpgrade(db, oldVersion, newVersion, USER_TABLE_NAME);
    }
} public class MessageTable {

    public static final String KEY_ROWID = "_id";
    public static final String KEY_ID = "id";
    public static final String KEY_CHANNEL = "channel";
    public static final String KEY_SENDER = "sender";
    public static final String KEY_IMAGE = "image";
    public static final String KEY_COUNT = "count";
    public static final String KEY_MESSAGE = "message";
    public static final String KEY_TYPE = "type";
    public static final String KEY_METADATA = "metadata";
    public static final String KEY_UPDATED = "received";
    public static String SQLITE_TABLE;

    public static final String[] ALL_PROJECTION = {
        MessageTable.KEY_CHANNEL,
        MessageTable.KEY_ID,
        MessageTable.KEY_SENDER ,
        MessageTable.KEY_IMAGE ,
        MessageTable.KEY_COUNT ,
        MessageTable.KEY_MESSAGE ,
        MessageTable.KEY_TYPE ,
        MessageTable.KEY_METADATA ,
        MessageTable.KEY_UPDATED };

    public static void onCreate(SQLiteDatabase db, String tableName) {
        SQLITE_TABLE = tableName;

        db.execSQL("DROP TABLE IF EXISTS " + tableName);

        String DATABASE_CREATE =
                "CREATE TABLE if not exists " + tableName + " (" +
                        KEY_ROWID + " integer PRIMARY KEY autoincrement," +
                        KEY_CHANNEL + ", " +
                        KEY_ID + "," +
                        KEY_SENDER + "," +
                        KEY_IMAGE + "," +
                        KEY_COUNT + " integer ," +
                        KEY_MESSAGE + "," +
                        KEY_TYPE + " integer , " +
                        KEY_METADATA + " ,"+
                        KEY_UPDATED + " integer );";

        db.execSQL(DATABASE_CREATE);
    }

    public static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion, String tableName) {
        SQLITE_TABLE = tableName;

        db.execSQL("DROP TABLE IF EXISTS " + tableName);
        onCreate(db, tableName);
    }
} public class XPushMessageDataSource extends DataSource<XPushMessage> {

    public final static String TAG = XPushMessageDataSource.class.getSimpleName();

    private String mTableName;
    private String mUserTableName;

    public XPushMessageDataSource(SQLiteDatabase database, String tableName) {
        super(database);
        this.mTableName = tableName;
        this.mUserTableName = null;
    }

    public XPushMessageDataSource(SQLiteDatabase database, String tableName, String userTableName) {
        super(database);
        this.mTableName = tableName;
        this.mUserTableName = userTableName;
    }

    @Override
    public boolean insert(XPushMessage entity) {
        if (entity == null) {
            return false;
        }
        long result = mDatabase.insert(mTableName, null,
                generateContentValuesFromObject(entity));
        return result != -1;
    }

    @Override
    public boolean delete(XPushMessage entity) {
        if (entity == null) {
            return false;
        }
        int result = mDatabase.delete(mTableName,
                MessageTable.KEY_ID + " = " + entity.getId(), null);
        return result != 0;
    }

    @Override
    public boolean update(XPushMessage entity) {
        if (entity == null) {
            return false;
        }
        int result = mDatabase.update(mTableName,
                generateContentValuesFromObject(entity), MessageTable.KEY_ID + " = "
                        + entity.getId(), null);
        return result != 0;
    }

    @Override
    public List<XPushMessage> read() {

        Cursor cursor = mDatabase.query(mTableName, MessageTable.ALL_PROJECTION, null,
                null, null, null, null);
        List messages = new ArrayList();
        if (cursor != null && cursor.moveToFirst()) {
            while (!cursor.isAfterLast()) {
                messages.add(generateObjectFromCursor(cursor));
                cursor.moveToNext();
            }
            cursor.close();
        }
        return messages;
    }

    @Override
    public List<XPushMessage> read(String selection, String[] selectionArgs,
                           String groupBy, String having, String orderBy) {

        Cursor cursor = null;

        if( "".equals( mUserTableName ) || mUserTableName == null ){
            cursor = mDatabase.query(mTableName, MessageTable.ALL_PROJECTION, selection, selectionArgs, groupBy, having, orderBy);
        } else {
            String messageTableAlias = "a";
            String userTableAlias = "b";

            StringBuffer query = new StringBuffer();
            query.append("select ");
            query.append(MessageTable.KEY_CHANNEL).append(", ");
            query.append(messageTableAlias+"."+MessageTable.KEY_ID).append(", ");
            query.append(UserTable.KEY_NAME).append(" as sender, ");
            query.append(userTableAlias+"."+UserTable.KEY_IMAGE).append(" as image, ");
            query.append(MessageTable.KEY_COUNT).append(", ");
            query.append(messageTableAlias+"."+MessageTable.KEY_MESSAGE).append(", ");
            query.append(messageTableAlias+"."+MessageTable.KEY_TYPE).append(", ");
            query.append(messageTableAlias+"."+MessageTable.KEY_METADATA).append(", ");
            query.append(messageTableAlias+"."+MessageTable.KEY_UPDATED).append(" ");
            query.append(" from " + mTableName + " " + messageTableAlias + " LEFT outer join " + mUserTableName + " " + userTableAlias);
            query.append(" on " + messageTableAlias + "." + MessageTable.KEY_SENDER + "=" + userTableAlias + "." + UserTable.KEY_ID);

            cursor = mDatabase.rawQuery(query.toString() +" where " +selection+ " order by " + orderBy , selectionArgs);
        }

        List messages = new ArrayList();
        if (cursor != null && cursor.moveToFirst()) {
            while (!cursor.isAfterLast()) {
                messages.add(generateObjectFromCursor(cursor));
                cursor.moveToNext();
            }
            cursor.close();
        }

        return messages;
    }

    @Override
    public int count() {
        Cursor mCount = mDatabase.rawQuery("select count(*) from " + mTableName, null );
        mCount.moveToFirst();
        int count = mCount.getInt(0);
        mCount.close();

        return count;
    }

    @Override
    public int count(String selection, String[] selectionArgs) {
        Cursor mCount = mDatabase.rawQuery("select count(*) from " + mTableName + " where " + selection, selectionArgs);
        mCount.moveToFirst();
        int count = mCount.getInt(0);
        mCount.close();

        return count;
    }

    public XPushMessage generateObjectFromCursor(Cursor cursor) {
        if (cursor == null) {
            return null;
        }
        XPushMessage message = new XPushMessage(cursor);
        return message;
    }

    public ContentValues generateContentValuesFromObject(XPushMessage entity) {
        if (entity == null) {
            return null;
        }
        ContentValues values = new ContentValues();
        values.put(MessageTable.KEY_CHANNEL, entity.getChannel());
        values.put(MessageTable.KEY_ID, entity.getId());
        values.put(MessageTable.KEY_SENDER, entity.getSenderId());
        values.put(MessageTable.KEY_IMAGE, entity.getImage());
        values.put(MessageTable.KEY_COUNT, entity.getCount());
        values.put(MessageTable.KEY_MESSAGE, entity.getMessage());
        values.put(MessageTable.KEY_TYPE, entity.getType());
        values.put(MessageTable.KEY_UPDATED, entity.getUpdated());
        if( entity.getMetadata() != null ){
            values.put(MessageTable.KEY_METADATA, entity.getMetadata().toString());
        }
        return values;
    }
} public class XpushContentProvider extends ContentProvider {

    public static final String TAG = XpushContentProvider.class.getSimpleName();

    public static final String SQL_INSERT_OR_REPLACE = "SQL_INSERT_OR_REPLACE";

    private static final int ALL_CHANNELS = 10;
    private static final int SINGLE_CHANNEL = 11;

    private static final int ALL_MESSAGES = 20;
    private static final int SINGLE_CHANNEL_MESSAGE = 21;
    private static final int SINGLE_MESSAGE = 22;

    private static final int ALL_USERS = 30;
    private static final int SINGLE_USER = 31;

    public static String CHANNEL_URI_STRING;
    public static String MESSAGE_URI_STRING;
    public static String USER_URI_STRING;

    public static Uri CHANNEL_CONTENT_URI;
    public static Uri MESSAGE_CONTENT_URI;
    public static Uri USER_CONTENT_URI;

    private static String AUTHORITY;
    private static UriMatcher uriMatcher;

    private static String CHANNEL_TABLE;
    private static String MESSAGE_TABLE;
    private static String USER_TABLE;

    private DBHelper dbHelper;

    @Override
    public boolean onCreate() {
        // get access to the database helper

        AUTHORITY = getContext().getString(R.string.content_provider_authority);
        CHANNEL_TABLE = getContext().getString(R.string.channel_table_name);
        MESSAGE_TABLE = getContext().getString(R.string.message_table_name);
        USER_TABLE = getContext().getString(R.string.user_table_name);

        CHANNEL_URI_STRING = "content://" + AUTHORITY + "/channels";
        MESSAGE_URI_STRING = "content://" + AUTHORITY + "/messages";
        USER_URI_STRING = "content://" + AUTHORITY + "/users";

        CHANNEL_CONTENT_URI = Uri.parse(CHANNEL_URI_STRING);
        MESSAGE_CONTENT_URI = Uri.parse(MESSAGE_URI_STRING);
        USER_CONTENT_URI = Uri.parse(USER_URI_STRING);

        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        uriMatcher.addURI(AUTHORITY, "channels", ALL_CHANNELS);
        uriMatcher.addURI(AUTHORITY, "channels/*", SINGLE_CHANNEL);

        uriMatcher.addURI(AUTHORITY, "messages", ALL_MESSAGES);
        uriMatcher.addURI(AUTHORITY, "messages/*", SINGLE_CHANNEL_MESSAGE);
        uriMatcher.addURI(AUTHORITY, "messages/*/*", SINGLE_MESSAGE);

        uriMatcher.addURI(AUTHORITY, "users", ALL_USERS);
        uriMatcher.addURI(AUTHORITY, "users/*", SINGLE_USER);

        dbHelper = new DBHelper(getContext());
        return false;
    }

    @Override
    public String getType(Uri uri) {

        switch (uriMatcher.match(uri)) {
            case ALL_CHANNELS:
                return "vnd.android.cursor.dir/vnd.contentprovider.channels";
            case SINGLE_CHANNEL:
                return "vnd.android.cursor.item/vnd.contentprovider.channels";
            case ALL_MESSAGES:
                return "vnd.android.cursor.dir/vnd.contentprovider.messages";
            case SINGLE_CHANNEL_MESSAGE:
                return "vnd.android.cursor.dir/vnd.contentprovider.messages.in";
            case SINGLE_MESSAGE:
                return "vnd.android.cursor.item/vnd.contentprovider.messages";
            case ALL_USERS:
                return "vnd.android.cursor.dir/vnd.contentprovider.users";
            case SINGLE_USER:
                return "vnd.android.cursor.item/vnd.contentprovider.users";
            default:
                throw new IllegalArgumentException("Unsupported URI: " + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {

        SQLiteDatabase db = dbHelper.getWritableDatabase();

        String tableName = "";
        Uri contentUri = null;

        switch (uriMatcher.match(uri)) {
            case ALL_CHANNELS:
                contentUri = CHANNEL_CONTENT_URI;
                tableName = CHANNEL_TABLE;
                break;
            case ALL_MESSAGES:
                contentUri = MESSAGE_CONTENT_URI;
                tableName = MESSAGE_TABLE;
                break;
            case ALL_USERS:
                contentUri = USER_CONTENT_URI;
                tableName = USER_TABLE;
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI: " + uri);
        }

        boolean replace = false;
        if ( values.containsKey( SQL_INSERT_OR_REPLACE ) ){
            replace = values.getAsBoolean( SQL_INSERT_OR_REPLACE );
            values.remove( SQL_INSERT_OR_REPLACE );
        }

        long rowId;
        if ( replace ) {
            rowId = db.replace(tableName, null, values);
        } else {
            rowId = db.insert(tableName, null, values);
        }

        getContext().getContentResolver().notifyChange(uri, null);
        return Uri.parse(contentUri + "/" + rowId);
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {

        SQLiteDatabase db = dbHelper.getWritableDatabase();
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

        String id = null;
        String tableName = null;

        switch (uriMatcher.match(uri)) {
            case ALL_CHANNELS:
                tableName = CHANNEL_TABLE;
                if ( sortOrder == null ){
                    sortOrder = ChannelTable.KEY_UPDATED + " DESC";
                }

                break;
            case SINGLE_CHANNEL:
                tableName = CHANNEL_TABLE;

                id = uri.getPathSegments().get(1);

                queryBuilder.appendWhere(ChannelTable.KEY_ID + "='" + id + "'");
                break;
            case ALL_MESSAGES:
                tableName = MESSAGE_TABLE;
                if ( sortOrder == null ){
                    sortOrder = MessageTable.KEY_UPDATED + " ASC";
                }

                break;
            case SINGLE_CHANNEL_MESSAGE:
                tableName = MESSAGE_TABLE;
                if ( sortOrder == null ){
                    sortOrder = MessageTable.KEY_UPDATED + " ASC";
                }

                String channel = uri.getPathSegments().get(1);
                queryBuilder.appendWhere(MessageTable.KEY_CHANNEL + "='" + channel+"'");
                break;
            case ALL_USERS:
                tableName = USER_TABLE;
                if ( sortOrder == null ){
                    sortOrder = UserTable.KEY_NAME + " ASC";
                }

                break;
            case SINGLE_USER:
                tableName = USER_TABLE;
                if ( sortOrder == null ){
                    sortOrder = UserTable.KEY_NAME + " ASC";
                }

                String userId = uri.getPathSegments().get(1);
                queryBuilder.appendWhere(UserTable.KEY_ID + "='" + userId+"'");
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI: " + uri);
        }

        queryBuilder.setTables(tableName);

        Cursor cursor = queryBuilder.query(db, projection, selection,
                selectionArgs, null, null, sortOrder);

        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {

        SQLiteDatabase db = dbHelper.getWritableDatabase();

        String id = null;
        String tableName = null;

        switch (uriMatcher.match(uri)) {
            case ALL_CHANNELS:
                tableName = CHANNEL_TABLE;
                break;
            case SINGLE_CHANNEL:
                tableName = CHANNEL_TABLE;
                id = uri.getPathSegments().get(1);
                selection = ChannelTable.KEY_ID + "='" + id+"'"
                        + (!TextUtils.isEmpty(selection) ?
                        " AND (" + selection + ')' : "");
                break;
            case ALL_MESSAGES:
                tableName = MESSAGE_TABLE;
                break;
            case SINGLE_CHANNEL_MESSAGE:
                tableName = MESSAGE_TABLE;

                String channel = uri.getPathSegments().get(1);
                selection = MessageTable.KEY_CHANNEL + "='" + channel+"'"
                        + (!TextUtils.isEmpty(selection) ?
                        " AND (" + selection + ')' : "");
                break;
            case ALL_USERS:
                tableName = USER_TABLE;
                break;
            case SINGLE_USER:
                tableName = USER_TABLE;
                id = uri.getPathSegments().get(1);
                selection = MessageTable.KEY_ID + "='" + id+"'"
                        + (!TextUtils.isEmpty(selection) ?
                        " AND (" + selection + ')' : "");
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI: " + uri);
        }

        int deleteCount = db.delete(tableName, selection, selectionArgs);

        getContext().getContentResolver().notifyChange(uri, null);
        return deleteCount;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        String id = null;
        String tableName = null;

        switch (uriMatcher.match(uri)) {
            case ALL_CHANNELS:
                tableName = CHANNEL_TABLE;
                //do nothing
                break;
            case SINGLE_CHANNEL:
                tableName = CHANNEL_TABLE;
                id = uri.getPathSegments().get(1);
                selection = ChannelTable.KEY_ID + "= '" + id + "'"
                        + (!TextUtils.isEmpty(selection) ?
                        " AND (" + selection + ')' : "");
                break;
            case ALL_MESSAGES:
                tableName = MESSAGE_TABLE;
                break;
            case SINGLE_MESSAGE:
                tableName = MESSAGE_TABLE;
                id = uri.getPathSegments().get(1);
                selection = MessageTable.KEY_ROWID + "='" + id+ "'"
                        + (!TextUtils.isEmpty(selection) ?
                        " AND (" + selection + ')' : "");
                break;
            case ALL_USERS:
                tableName = USER_TABLE;
                break;
            case SINGLE_USER:
                tableName = USER_TABLE;
                id = uri.getPathSegments().get(1);
                selection = MessageTable.KEY_ID + "='" + id+ "'"
                        + (!TextUtils.isEmpty(selection) ?
                        " AND (" + selection + ')' : "");
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI: " + uri);
        }

        int updateCount = db.update(tableName, values, selection, selectionArgs);

        getContext().getContentResolver().notifyChange(uri, null);
        return updateCount;
    }
} public class BadgeIntentService extends IntentService {

    private static final String TAG = BadgeIntentService.class.getSimpleName();

    public BadgeIntentService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        DBHelper mDbHelper = new DBHelper(getApplicationContext());
        SQLiteDatabase mDatabase = mDbHelper.getReadableDatabase();

        Cursor mCount = mDatabase.rawQuery("select sum(" + ChannelTable.KEY_COUNT + ") from " + getApplicationContext().getString(R.string.channel_table_name), null);
        mCount.moveToFirst();
        int count = mCount.getInt(0);
        mCount.close();
        mDatabase.close();
        mDbHelper.close();

        ShortcutBadger.with(getApplicationContext()).count(count);
    }
} public class PushMsgReceiver extends BroadcastReceiver {

    private static final int NOTIFICATION_ID = 0;
    private static final String TAG = PushMsgReceiver.class.getSimpleName();

    @Override
    public void onReceive(Context mContext, Intent intent) {
        if (intent != null) {

            String action = intent.getAction().toString();
            Bundle extras = intent.getExtras();

            Log.d(TAG, extras.toString());

            if( XPushCore.getXpushSession() == null){
                return;
            }

            if(action.isEmpty()){

            } else if ("io.xpush.chat.MGRECVD".equals(action)) {

                String channel = intent.getStringExtra("rcvd.C");
                String name = intent.getStringExtra("rcvd.NM");
                String message = intent.getStringExtra("rcvd.MG");
                int type= intent.getIntExtra("rcvd.TP", XPushMessage.TYPE_RECEIVE_MESSAGE);

                showNotification(mContext, name, channel, message, type);
            } else if("com.google.android.c2dm.intent.RECEIVE".equals(action)) {    // gcm msg receive
                if (!extras.isEmpty()) {
                    try {
                        JSONObject userData =  new JSONObject(extras.getString("UO"));
                        showNotification(mContext, userData.getString("NM"), extras.getString("C"), extras.getString("MG"));
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public void showNotification(Context mContext, String name, String channel, String message){
        showNotification( mContext, name, channel, message, XPushMessage.TYPE_RECEIVE_MESSAGE );
    }

    public void showNotification(Context mContext, String name, String channel, String message, int type) {
        Log.d(TAG, "showNotification");

        String startActivity = null;
        Class cls = null;
        try {
            startActivity = (String) mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA).metaData.get("INTRO_ACTIVITY");
            cls = Class.forName(startActivity);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        XPushChannel xpushChannel = new XPushChannel();
        xpushChannel.setId( channel );
        Bundle bundle = xpushChannel.toBundle();

        Intent intent = new Intent(mContext, cls);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        intent.putExtra(xpushChannel.CHANNEL_BUNDLE, bundle);

        PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);

        Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mContext);
        mBuilder.setSmallIcon(R.drawable.ic_launcher);//required
        mBuilder.setContentTitle(name);//required

        // IMAGE TYPE인 경우 사진이라고만 표시함
        if( type == XPushMessage.TYPE_SEND_IMAGE || type == XPushMessage.TYPE_RECEIVE_IMAGE ){
            mBuilder.setContentText( mContext.getString(R.string.title_for_image_type) );//required
        } else {
            mBuilder.setContentText(message);//required
        }
        mBuilder.setTicker( mContext.getString(R.string.app_id));//optional
        mBuilder.setAutoCancel(true);
        mBuilder.setContentIntent(contentIntent);

        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

        //long[] pattern = {200,200,300,300};
        long[] pattern = {500,500};
        int ringerMode = audioManager.getRingerMode();
        switch ( ringerMode ){
            case AudioManager.RINGER_MODE_NORMAL :
                mBuilder.setSound(defaultSoundUri);
                break;
            case AudioManager.RINGER_MODE_VIBRATE :
                mBuilder.setVibrate(pattern);
                break;
            case AudioManager.RINGER_MODE_SILENT :
                mBuilder.setVibrate(pattern);
                break;
            default:
                mBuilder.setSound(defaultSoundUri);
                break;
        }

        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);

        boolean isScreenOn = isInteractive(pm);

        Log.e("screen on.....", "" + isScreenOn);
        PowerManager.WakeLock wl=null, wl_cpu = null;

        if (isScreenOn == false) {
            wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "MyLock");
            wl.acquire(5000);
            wl_cpu = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyCpuLock");
            wl_cpu.acquire(5000);
        }

        NotificationManager mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());

        if (wl != null)
            wl.release();

        if(wl_cpu != null)
            wl_cpu.release();

        // Reset Badge
        Intent badgeIntent = new Intent(mContext,BadgeIntentService.class);
        mContext.startService(badgeIntent);
    }

    public static boolean isInteractive(PowerManager pm) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
            return pm.isInteractive();
        } else {
            return !pm.isScreenOn();
        }
    }
} public class MessageListAdapter extends RecyclerView.Adapter<MessageListAdapter.ViewHolder> {

    private List<XPushMessage> mXPushMessages;
    private static MessageClickListener mMessageClickListener;
    private Context mContext;

    public MessageListAdapter(Context context, List<XPushMessage> xpushMessages) {
        mContext = context;
        mXPushMessages = xpushMessages;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        int layout = -1;
        if (viewType == XPushMessage.TYPE_SEND_MESSAGE ) {
            layout = R.layout.item_send_message;
        } else if (viewType == XPushMessage.TYPE_RECEIVE_MESSAGE ) {
            layout = R.layout.item_receive_message;
        } else if (viewType == XPushMessage.TYPE_INVITE || viewType == XPushMessage.TYPE_LEAVE ) {
            layout = R.layout.item_invite_message;
        } else if (viewType == XPushMessage.TYPE_SEND_IMAGE ) {
            layout = R.layout.item_send_image;
        } else if (viewType == XPushMessage.TYPE_RECEIVE_IMAGE ) {
            layout = R.layout.item_receive_image;
        }

        View v = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        XPushMessage xpushMessage = mXPushMessages.get(position);

        viewHolder.setUsername(xpushMessage.getSenderName());
        viewHolder.setTime(xpushMessage.getUpdated());
        viewHolder.setImage(xpushMessage.getImage());
        viewHolder.setMessage(xpushMessage.getMessage(), xpushMessage.getType(), xpushMessage.getMetadata());
    }

    @Override
    public int getItemCount() {
        return mXPushMessages.size();
    }

    @Override
    public int getItemViewType(int position) {
        return mXPushMessages.get(position).getType();
    }

    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
        private TextView tvUser;

        private TextView tvTime;
        private SimpleDraweeView thumbNail;
        private View vMessage;
        private View vClickableView;

        private ViewHolder(View itemView) {
            super(itemView);

            tvTime = (TextView) itemView.findViewById(R.id.tvTime);
            tvUser = (TextView) itemView.findViewById(R.id.tvUser);
            thumbNail = (SimpleDraweeView) itemView.findViewById(R.id.thumbnail);
            vMessage = (View) itemView.findViewById(R.id.vMessage);
            vClickableView = (View)itemView.findViewById(R.id.bubble);
            vClickableView.setOnClickListener(this);
            vClickableView.setOnLongClickListener(this);
        }

        @Override
        public void onClick(View view) {
            int position  = ViewHolder.super.getAdapterPosition();
            mMessageClickListener.onMessageClick(mXPushMessages.get(position).getMessage(), mXPushMessages.get(position).getType());
        }

        @Override
        public boolean onLongClick(View view) {
            int position  = ViewHolder.super.getAdapterPosition();
            mMessageClickListener.onMessageLongClick(mXPushMessages.get(position).getMessage(), mXPushMessages.get(position).getType());
            return true;
        }

        public void setUsername(String username) {
            if (null == tvUser) return;
            tvUser.setText(username);
        }

        public void setMessage(String message, int type, final JSONObject metatdata) {

            if (null == vMessage) return;
            if( type == XPushMessage.TYPE_SEND_IMAGE || type == XPushMessage.TYPE_RECEIVE_IMAGE ) {


                final ImageView imageView = ((ImageView) vMessage);
                if( metatdata != null ){
                    try {
                        int metaWidth = metatdata.getInt("W");
                        int metaHeight = metatdata.getInt("H");

                        if( metaWidth > 0 && metaHeight > 0 ) {
                            int results[] = ContentUtils.getActualImageSize(metaWidth, metaHeight, mContext);
                            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(results[0], results[1]);
                            imageView.setLayoutParams(layoutParams);
                        }
                    } catch ( JSONException e ){
                        e.printStackTrace();
                    }
                }

                Glide.with(mContext)
                    .load(Uri.parse(message))
                    .asBitmap()
                    .diskCacheStrategy(DiskCacheStrategy.ALL)
                    .fitCenter()
                    .into(new SimpleTarget<Bitmap>() {
                        @Override
                        public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
                            int originalWidth = bitmap.getWidth();
                            int originalHeight =  bitmap.getHeight();

                            if( metatdata == null ) {
                                //int results[] = ContentUtils.getActualImageSize(originalWidth, originalHeight, mContext);
                                //LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(results[0], results[1]);
                                //imageView.setLayoutParams(layoutParams);
                            }
                            imageView.setImageBitmap(bitmap);
                        }
                    });
            } else {
                ( (TextView) vMessage ).setText(message);
            }
        }

        public void setTime(long timestamp) {
            if (null == tvTime) return;
            tvTime.setText(DateUtils.getDate(timestamp, "a h:mm"));
        }

        public void setImage(String image) {
            if (null == image || null == thumbNail) return;
            thumbNail.setImageURI(Uri.parse(image));
        }
    }

    public void setOnItemClickListener(MessageClickListener clickListener) {
        MessageListAdapter.mMessageClickListener = clickListener;
    }

    public interface MessageClickListener  {
        public void onMessageClick(String message, int type);

        public void onMessageLongClick(String message, int type);
    }
} public abstract class RecyclerOnScrollListener extends RecyclerView.OnScrollListener {
    public static String TAG = RecyclerOnScrollListener.class.getSimpleName();

    private int previousTotal = 0; // The total number of items in the dataset after the last load
    private boolean loading = true; // True if we are still waiting for the last set of data to load.
    private int visibleThreshold = 5; // The minimum amount of items to have below your current scroll position before loading more.
    int firstVisibleItem, visibleItemCount, totalItemCount;

    public enum RecylclerDirection{
        UP, DOWN
    }

    private int current_page = 1;
    private RecylclerDirection mDirection;

    private LinearLayoutManager mLinearLayoutManager;

    public RecyclerOnScrollListener(LinearLayoutManager linearLayoutManager, RecylclerDirection direction) {
        this.mDirection = direction;
        this.mLinearLayoutManager = linearLayoutManager;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        if( RecylclerDirection.UP == mDirection ) {
            // if scroll to bottom
            if (dy > 0) {
                return;
            }

        } else {
            // if scroll to up
            if (dy < 0) {
                return;
            }
        }

        visibleItemCount = recyclerView.getChildCount();
        totalItemCount = mLinearLayoutManager.getItemCount();
        firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();

        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false;
                previousTotal = totalItemCount;
            }
        }

        if( RecylclerDirection.UP == mDirection ) {

            // Thread Hold reached
            if (!loading && firstVisibleItem <= visibleThreshold) {
                // Do something
                current_page++;
                onLoadMore(current_page);
                loading = true;
            }

        } else {

            if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {

                // End has been reached
                // Do something
                current_page++;
                onLoadMore(current_page);
                loading = true;
            }
        }
    }

    public abstract void onLoadMore(int current_page);

    public void reset(int previousTotal, boolean loading) {
        this.previousTotal = previousTotal;
        this.loading = loading;
    }
}
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import io.socket.emitter.Emitter;
import io.socket.engineio.client.EngineIOException;
import io.socket.client.Socket;
import io.socket.client.SocketIOException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import io.xpush.chat.R;
import io.xpush.chat.core.CallbackEvent;
import io.xpush.chat.core.ChannelCore;
import io.xpush.chat.core.XPushCore;
import io.xpush.chat.loaders.MessageDataLoader;
import io.xpush.chat.models.XPushChannel;
import io.xpush.chat.models.XPushMessage;
import io.xpush.chat.models.XPushSession;
import io.xpush.chat.models.XPushUser;
import io.xpush.chat.persist.ChannelTable;
import io.xpush.chat.persist.DBHelper;
import io.xpush.chat.persist.MessageTable;
import io.xpush.chat.persist.XPushMessageDataSource;
import io.xpush.chat.persist.XpushContentProvider;
import io.xpush.chat.services.BadgeIntentService;
import io.xpush.chat.services.PushMsgReceiver;
import io.xpush.chat.view.adapters.MessageListAdapter;
import io.xpush.chat.view.listeners.RecyclerOnScrollListener;
package io.xpush.chat.fragments;






public abstract class XPushChatFragment extends Fragment implements LoaderManager.LoaderCallbacks<List<XPushMessage>>{

    public static final String TAG = XPushChatFragment.class.getSimpleName();
    protected static final int INIT_VIEW_COUNT = 16;

    private int mViewCount;

    private RecyclerView mRecyclerView;
    private EditText mInputMessageView;
    protected MessageListAdapter mAdapter;
    private boolean mTyping = false;

    private String mUserId;
    private String mUsername;

    private XPushChannel mXpushChannel;

    protected String mChannel;
    protected Activity mActivity;
    protected ArrayList<String> mUsers = new ArrayList<String>();

    private XPushSession mSession;

    private SQLiteDatabase mDatabase;
    private XPushMessageDataSource mDataSource;
    private DBHelper mDbHelper;

    private List<XPushMessage> mXpushMessages = new ArrayList<XPushMessage>();
    protected ArrayList<XPushUser> mXpushUsers = new ArrayList<XPushUser>();

    private MessageDataLoader mDataLoader;
    private RecyclerOnScrollListener mOnScrollListener;

    private LinearLayoutManager mLayoutManager;

    protected ChannelCore mChannelCore;

    public XPushChatFragment() {
        super();
    }

    private long lastReceiveTime = 0L;

    protected boolean newChannelFlag;
    protected boolean resetChannelFlag;
    protected boolean restartLoader;

    private Bundle mChannelBundle;

    private Bundle mSavedInstanceState;


    private AtomicBoolean isPaused = new AtomicBoolean();

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mAdapter = new MessageListAdapter(activity, mXpushMessages);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mViewCount = INIT_VIEW_COUNT;
        mActivity = getActivity();

        mDbHelper = new DBHelper(mActivity);
        mDatabase = mDbHelper.getWritableDatabase();
        mDataSource = new XPushMessageDataSource(mDatabase, getActivity().getString(R.string.message_table_name), getActivity().getString(R.string.user_table_name));

        mSession = XPushCore.getXpushSession();
        newChannelFlag = mActivity.getIntent().getBooleanExtra("newChannel", false);

        if( savedInstanceState == null ){
            mChannelBundle = mActivity.getIntent().getBundleExtra(XPushChannel.CHANNEL_BUNDLE);

        } else {
            mChannelBundle = savedInstanceState.getBundle(XPushChannel.CHANNEL_BUNDLE);
            mViewCount = savedInstanceState.getInt("mViewCount");
            if( mDataLoader == null  ){

                resetChannelFlag = true;
                restartLoader = true;
            }
        }

        mXpushChannel = new XPushChannel(mChannelBundle);
        mChannel = mXpushChannel.getId();

        if (null == mXpushChannel.getName()) {

            Uri singleUri = Uri.parse(XpushContentProvider.CHANNEL_CONTENT_URI + "/" + mChannel);