레이블이 sample인 게시물을 표시합니다. 모든 게시물 표시
레이블이 sample인 게시물을 표시합니다. 모든 게시물 표시

2014년 4월 17일 목요일

Tizen Multi Proc Service/UI App sample

아래 내용은 Tizen 2.2 기반이라 상위 버전에서는 별 의미 없는 내용입니다.
--------------------------------------------------------------------------- : https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/sample_descriptions/multiprocserviceapp.htm

: https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/sample_descriptions/multiprocserviceapp.htm

Tizen에서 ServiceApp과 UiApp간의 interaction을 알 수 있는 샘플
Timer event를 발생 시키는 ServiceApp과 이 ServiceApp에서 event를 받아 Ui에 표시해주는 application sample.

Service와 app간 통신은 IMessagePortListener를 상속받아서 메세지를 주고 받으며 처리한다.

실행해 보려면 Documentation에 나와 있다 시피
service app의 id와 service name을 manifest.xml에서 확인한 뒤
ui app의 SampleUiApp에서 아래 부분을 수정해 줘야 한다.


String serviceName(L".multiprocserviceapp");
String repAppId(15);

repAppId = L"LVjRUjlONC";

AppId serviceId(repAppId+serviceName);


[Tizen::Io::IMessagePortListener]

: https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.apireference/classTizen_1_1Io_1_1IMessagePortListener.html


이것 저것 다 빼고 발로 그린 class diagram


[MultiProcServiceApp]


[SampleServiceApp]
Tizen에서 UI feature를 가지지 않고 background에서 실행될 수 있는 ServiceApp을 상속 받는 class이며 background로 실행되어 Timer를 제어하여 상황 변경 시 event를 app으로 보내주는 것임.

초기화 시점에 App과의 통신을 위한 SampleMessagePort class와 Timer를 제어, event를 발생하는 SampleTimer를 생성하며 SampleMessagePort 생성(construct)시 port name으로 사용할 문자열을 전달하여 port를 생성하도록 한다.


[Message Port Communication]

: https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/guide/io/messageport.htm





bool
SampleServiceApp::OnAppInitializing(AppRegistry& appRegistry)
{
AppLog("SampleServiceApp::OnAppInitializing is called.");
result r = E_SUCCESS;

// Initialize ServerChannel
__pMessagePort = new (std::nothrow) SampleMessagePort();
TryReturn(__pMessagePort != null, false, "SampleServiceApp : [E_FAILURE] Failed to create __pMessagePort.");
AppLog("SampleServiceApp : __pMessagePort is created.");

r = __pMessagePort->Construct(LOCAL_MESSAGE_PORT_NAME);
TryReturn(IsFailed(r) != true, r, "SampleServiceApp : [%s] Failed to construct __pMessagePort", GetErrorMessage(r));
AppLog("SampleServiceApp : __pMessagePort is constructed.");

// Initialize Timer
__pTimer = new (std::nothrow) SampleTimer;
TryReturn(__pTimer != null, false, "SampleServiceApp : [E_FAILURE] Failed to create __pTimer.");
AppLog("SampleServiceApp : __pTimer is created.");

r = __pTimer->Construct();
TryReturn(IsFailed(r) != true, r, "SampleServiceApp : [%s] Failed to construct __pTimer", GetErrorMessage(r));
AppLog("SampleServiceApp : __pTimer is constructed.");

return true;
}



아래의 Tizen::Ui::Control의 가상함수인 OnUserEventReceivedN()를 구현 하여
SampleTimer로 부터 전달 받은 event를 App으로 전달한다.

void
SampleServiceApp::OnUserEventReceivedN(RequestId requestId, IList* pArgs)
{
switch (requestId)
{
case TIMER_START :
if (__pTimer != null)
{
__pTimer->Start();
}
break;
case TIMER_STOP :
if (__pTimer != null)
{
__pTimer->Stop();
}
Terminate();
break;
case TIMER_EXPIRED :
if (__pMessagePort != null)
{
HashMap *pMap = new HashMap(SingleObjectDeleter);
pMap->Construct();
pMap->Add(new String(L"ServiceApp"), new String(L"timer expired"));

__pMessagePort->SendMessage(pMap);

delete pMap;
}
break;
case TIMER_EXIT :
Terminate();
break;
default:
break;
}
}


[SampleMessagePort]

SampleMessagePort는 message를 수신받기위해 Tizen::Io::IMessagePortListener를 구현하였음.
위 그림에서와 같이 수신받는 port인 local port, 전달하는 port인 remote port를 멤버 변수로 가지고 있어 메시지 수신 및 발송 까지 처리하는 class 임

local port는 MessagePortManager를 통해서 생성하고 remote port는 listener에서 connect가 되었을때 전달 받음.

result
SampleMessagePort::Construct(const String& localPortName)
{
result r = E_SUCCESS;

__pLocalMessagePort = MessagePortManager::RequestLocalMessagePort(localPortName);
__pLocalMessagePort->AddMessagePortListener(*this);
...
return r;
}

void
SampleMessagePort::OnMessageReceivedN(RemoteMessagePort* pRemoteMessagePort, IMap* pMessage)
{
String *pData = static_cast<String *>(pMessage->GetValue(String(L"UiApp")));

AppLog("SampleServiceApp : Received data : %ls", pData->GetPointer());

HashMap *pMap = new HashMap(SingleObjectDeleter);
pMap->Construct();

if (pData->CompareTo(L"connect") == 0)
{
__pRemoteMessagePort = pRemoteMessagePort;
pMap->Add(new String(L"ServiceApp"), new String(L"ready"));
}
...
}


result
SampleMessagePort::SendMessage(const IMap* pMessage)
{
...
r = __pRemoteMessagePort->SendMessage(__pLocalMessagePort, pMessage);
...
}


[SampleTimer]

Timer event 수신을 위해서 Tizen::Base::Runtime::ITimerEventListener 를 상속 받음.
Timer가 expire 될 경우 SampleServiceApp으로 TIMER_EXPIRED event를 전달함.

void
SampleTimer::OnTimerExpired(Timer& timer)
{
AppLog("SampleServiceApp : __pTimer is expired.");

if (__isRunning)
{
App* pApp = App::GetInstance();
if (pApp != null)
{
ArrayList messageList;
messageList.Construct();

pApp->SendUserEvent(TIMER_EXPIRED, &messageList);

messageList.RemoveAll(true);
}

if (__isRepeatable)
{
timer.Start(TIMER_INTERVAL_RUN);
}
}
else
{
timer.Start(TIMER_INTERVAL_READY);
}
}


-----------------------------------------------------------------------------------------------------

[MultiProcUiApp]

이것 저것 다 빼고 발로 그린 class diagram


[SampleUiApp]

SampleUiApp에서는 GUI 구성에 필요한 frame과 service와 통신을 위해 사용하는 SampleServiceProxy를 생성한다.

AppId(String일 뿐이다.)를 생성한뒤 SampleServiceProxy 생성 후 초기화 때 인자로 넘겨 준다.

bool
SampleUiApp::OnAppInitialized(void)
{
...
String serviceName(L".MultiProcServiceApp");
String repAppId(15);

repAppId = L"qwertzxcvb";

AppId serviceId(repAppId+serviceName);

AppLog("SampleUiApp : Service Id is %ls", serviceId.GetPointer());

// Initialize ServiceProxy.
result r = E_SUCCESS;
__pService = new (std::nothrow) SampleServiceProxy();
r = __pService->Construct(serviceId, REMOTE_PORT_NAME);
if (IsFailed(r))
{
AppLog("SampleUiApp : [%s] SeviceProxy creation is failed.", GetErrorMessage(r));
__pForm->SendUserEvent(STATE_FAIL, null);
}
else
{
__isReady = true;
}

return true;
}


Service로부터 메시지를 수신 받거나 전송하기 위해
Tizen::Base::Runtime::IEventListener를 구현하였음.
requestId에 따라서 명령을 처리하며 위에서 초기화 때 생성한 SampleServiceProxy를 사용하여
메시지 전송


void
SampleUiApp::OnUserEventReceivedN(RequestId requestId, IList* pArgs)
{
AppLog("SampleUiApp : OnUserEventReceivedN is called. requestId is %d", requestId);

result r = E_SUCCESS;

switch (requestId)
{
case STATE_CONNECT_REQUEST :
if (__isReady)
{
HashMap *pMap = new HashMap(SingleObjectDeleter);
pMap->Construct();
pMap->Add(new String(L"UiApp"), new String(L"connect"));

r = __pService->SendMessage(pMap);

delete pMap;

TryReturnVoid(!IsFailed(r), "SampleUiApp : [%s] MessagePort Operation is Failed", GetErrorMessage(r));
}
break;
...
}


[SampleServiceProxy]

Tizen::Io::IMessagePortListener 를 상속 받아 port를 생성, 관리하는 class.

초반에는 Service가 동작하는지를 AppManager를 통해서 확인하고 없다면 Service를 실행한다.
그리고 Service에서는 local port를 반들고 remote port는 connect 때 만들어 졌는 것에 반해
여기서는 local port와 remote port를 명시적으로 생성한다.


result
SampleServiceProxy::Construct(const AppId& appId, const String& remotePortName)
{
...
AppManager* pAppManager = AppManager::GetInstance();
...
for (int i=0; i < 5; ++i)
{
if (pAppManager->IsRunning(__appId))
{
AppLog("SampleUiApp : Service is ready !!!");
break;
}
else
{
AppLog("SampleUiApp : Service is not ready !!! try to launch !!! ");
r = pAppManager->LaunchApplication(__appId, null);
TryReturn(!IsFailed(r), r, "SampleUiApp : [%s]", GetErrorMessage(r));
Thread::Sleep(CHECK_INTERVAL);
}
}

...
__pLocalMessagePort = MessagePortManager::RequestLocalMessagePort(LOCAL_MESSAGE_PORT_NAME);
__pLocalMessagePort->AddMessagePortListener(*this);

__pRemoteMessagePort = MessagePortManager::RequestRemoteMessagePort(appId, remotePortName);

...
}


Form이랑 Frame은 일반적이라 생략...

Tizen BasicApp sample

아래 내용은 Tizen 2.2 기반이라 상위 버전에서는 별 의미 없는 내용입니다.
--------------------------------------------------------------------------- : https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/sample_descriptions/basicapp.htm

https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/tutorials/ui_tutorial/task_basicapp_panels.htm

Tizen sample 중 가장 기본 앱인 basic app을 대충 훑어봄...
간단하게 Tab header를 통해 Button, Orientation, Image panel에 있는
 button, image, orientation 변경을 확인할 수 있음.


Button  Image

이것저것 다 생략하고 발로 그린 class diagram.


[BasicAppEntry.cpp]

Tizen에서는 memory에 필요 library들과 application executable을 load하고
application executable의 OspMain() 함수를 호출하여 실행.
OspMain()함수에서는 BasicApp의 instance 생성을 함

int
OspMain(int argc, char* pArgv[])
{
...
result r = Tizen::App::Application::Execute(BasicApp::CreateInstance, &args);
...
}


[BasicApp.cpp]

BasicApp에서는 app을 system에 등록하고 app UI 표시를 위해 BasicAppFram을 생성하여 등록함.
BasicApp은 Application(UiApp)을 상속 받고 Back key 처리를 위해서 IScreenEventListener를 구현함.


Tizen::App::Application(UiApp), Tizen::App::ServiceApp
: https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/guide/app/service_app_fundamentals.htm
문서에서 나와 있듯이 일반적인 application은 Application을 상속받아 구현하면 되며
UI를 가지지 않고 background로 동작하는 app을 ServiceApp을 상속받아서 만들 수 있음.


AppRegistry
: https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.apireference/classTizen_1_1App_1_1AppRegistry.html
: 설명에서는 app의 preference들을 저장하고 사용하기 위해 사용하는 class라고 하는데 아무래도 system에서 app 관리를 위해서도 사용되는 class일 것 같다... (tizen 소스를 보면 알겠지만...귀찮음)


bool
BasicApp::OnAppInitializing(AppRegistry& appRegistry)
{
int lastPanelId = 0;
String lastPanelName(L"");
String panelIDkey(L"AppLastPanelId");
String panelNamekey(L"AppLastPanelName");

result r = appRegistry.Get(panelIDkey, lastPanelId);
if (r == E_KEY_NOT_FOUND)
{
lastPanelId = 0;
appRegistry.Add(panelIDkey, lastPanelId);
}

r = appRegistry.Get(panelNamekey, lastPanelName);
if (r == E_KEY_NOT_FOUND)
{
appRegistry.Add(panelNamekey, L"Panel1");
}

// Create a Frame
BasicAppFrame* pBasicAppFrame = new (std::nothrow) BasicAppFrame();
pBasicAppFrame->Initialize(lastPanelId);
pBasicAppFrame->SetName(L"BasicApp");
AddFrame(*pBasicAppFrame);

return true;
}


[BasicAppFrame]

Tizen GUI 구성에 기본이 되는 Frame class을 상속 받은 class로 실제 App에서 사용될 MainForm을 생성&등록하는 역할을 한다.



Tizen User Interface
: https://developer.tizen.org/dev-guide/2.2.0/org.tizen.native.appprogramming/html/guide/ui/ui_namespace.htm


result
BasicAppFrame::OnInitializing(void)
{
// Create a form
MainForm* pMainForm = new (std::nothrow) MainForm();
result r = pMainForm->Initialize(__panelId);

// Add the form to the frame
AddControl(pMainForm);

// Set the current form
SetCurrentForm(pMainForm);

// Draw the form
pMainForm->Invalidate(true);

return r;
}


[MainForm]

BasicApp의 기본 GUI를 구성하는 class
기본으로 Tizen::Ui::Controls::Form을 상속 받고 Action event handling을 위해 IActionEventListener와
Back key 처리를 위해 IFormBackEventListener를 구현 한다.


class MainForm 
: public Tizen::Ui::Controls::Form
, public Tizen::Ui::IActionEventListener
, public Tizen::Ui::Controls::IFormBackEventListener
{
...

private:

Tizen::Ui::Controls::Panel* __pPanel[3];
Tizen::Ui::Controls::Button* __pButtonOrientation;
Tizen::Ui::Orientation __status;

int __panelId;
};


초기화 시점에서 GUI를 구성하는 위쪽 Tab들, ButtonPannel을 구성하고
tab들의 상태를 초기화 하기 위한 코드가 있음.
이전 실행 종료 시점에 저장해 둔 tab 위치(__panelId)에 따라서 보여지게 하기 위한 코드

좀 다른 얘기이지만 SceneManager를 사용하지 않지만 SceneManager에는 FormFactory와 PanelFactory를 등록하여 id를 사용해서 생성하게 하는 부분이 있음.. 좀 이대로 해보려고 했으나 Form 생성은 되지만 Panel 생성은 안되는 것 같다.. 나중에 좀 더 봐야 할듯.
https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/tutorials/ui_tutorial/registering_scenes.htm

result
MainForm::OnInitializing(void)
{
result r = E_SUCCESS;

Rectangle clientRect = GetClientAreaBounds();
Rectangle rect(0, 0, clientRect.width, clientRect.height);

// Create header
Header* pHeader = GetHeader();
if (pHeader != null)
{
pHeader->SetStyle(HEADER_STYLE_TAB);

HeaderItem headerItem1;
headerItem1.Construct(ID_HEADER_ITEM1);
headerItem1.SetText(L"Button");
pHeader->AddItem(headerItem1);

...

pHeader->AddActionEventListener(*this);
}

SetFormBackEventListener(this);

// Create the Button panel
ButtonPanel* pButtonPanel = new (std::nothrow) ButtonPanel();
pButtonPanel->Initialize(rect);
AddControl(pButtonPanel);
__pPanel[0] = pButtonPanel;

// Orientation panel was created with UI Builder,
// so only its button events must be defined here
__pPanel[1] = static_cast<Panel *>(GetControl(IDC_ORIENTATIONPANEL));
if (__pPanel[1] != null)
{
__pButtonOrientation = static_cast<Button *>(GetControl(IDC_BUTTON_ORIENTATION, true));

if (__pButtonOrientation != null)
{
__pButtonOrientation->SetActionId(ID_ORIENTATION);
__pButtonOrientation->AddActionEventListener(*this);
}
}

// Set the current panel as selected in the header and display it on the form
if (pHeader)
{
pHeader->SetItemSelected(__panelId);

if(__panelId == 2)
{
if (__pPanel[2] == null)
{
CreateImagePanel();
}
SetOrientation(ORIENTATION_AUTOMATIC);
}

if (__pPanel[0] != null)
{
__pPanel[0]->SetShowState(false);
}
if (__pPanel[1] != null)
{
__pPanel[1]->SetShowState(false);
}

__pPanel[__panelId]->SetShowState(true);
}

Invalidate(true);
return r;
}


Action event 처리를 위한 IActionEventListener 구현 부
Tab 처리를 위한 코드는 좀 노가다 같다..
사실 tab widget을 system에서 제공해 주면 이런 코드는 안봐도 될것 같은데..


void
MainForm::OnActionPerformed(const Tizen::Ui::Control& source, int actionId)
{
switch(actionId)
{
case ID_HEADER_ITEM1:
{
if (__pPanel[0] != null)
{
__pPanel[0]->SetShowState(true);
}
if (__pPanel[1] != null)
{
__pPanel[1]->SetShowState(false);
}
if (__pPanel[2] != null)
{
__pPanel[2]->SetShowState(false);
}
SetOrientation(ORIENTATION_PORTRAIT);
}
break;

case ID_HEADER_ITEM2:
...
case ID_HEADER_ITEM3:
...
case ID_ORIENTATION:
{
if (__pPanel[1]->GetShowState())
{
OrientationStatus status = GetOrientationStatus();
if (status == ORIENTATION_STATUS_PORTRAIT)
{
__status = ORIENTATION_LANDSCAPE;
}
else if (status == ORIENTATION_STATUS_LANDSCAPE)
{
__status = ORIENTATION_PORTRAIT;
}
SetOrientation(__status);
}
}
break;

default:
break;
}

Invalidate(true);
}

종료 시점에는 AppRegistry를 통해 선택된 tab index를 저장해 둔다..
근데 왜 panelIDkey와 panelNamekey 두개로 저장하는 걸까?

result
MainForm::OnTerminating(void)
{
AppRegistry *appRegistry = Application::GetInstance()->GetAppRegistry();
String panelIDkey(L"AppLastPanelId");
String panelNamekey(L"AppLastPanelName");

Header* pHeader = GetHeader();
result r = appRegistry->Set(panelIDkey,pHeader->GetSelectedItemIndex());
if (IsFailed(r))
{
//error condition
}

String panel;
panel.Format(50, L"Panel%d", pHeader->GetSelectedItemIndex());
r = appRegistry->Set(panelNamekey, panel);
if (IsFailed(r))
{
//error condition
}

r = appRegistry->Save();
if (IsFailed(r))
{
//failed to save data to registry.
}

return r;
}


그리고 아래 Tutorial 괜찮음
: http://tizenschools.com/?p=557


2013년 4월 28일 일요일

Sample chat with Smack library

이전 블로그에서 이전 함 (원본 글 2013/04/28 작성)

* Smack library로 sample chat을 실행 해 봄.
* Server는 OpenFire 설치 후 계정 설정 후 테스트

* Android상에서 테스트 하려면 아래의 경로의 asmack.jar를 사용해야 한다.
* XMPP의 id는 email과 비슷하다는 것을 꼭 유념하고 상대 id에 domain이 포함되도록 해야한다.
  포함되지 않으면 메세지 전송 시 remote server not found (404) error를 리턴한다.




Smack library의 simple sample은 아래에서 보여주고 있음.
 // Create a connection to the igniterealtime.org XMPP server. Connection con = new XMPPConnection("igniterealtime.org");
 // Connect to the server
 con.connect();
 // Most servers require you to login before performing other tasks.
 con.login("jsmith", "mypass");
 // Start a new conversation with John Doe and send him a message.
 Chat chat = connection.getChatManager().createChat("jdoe@igniterealtime.org", new MessageListener() {
 
public void processMessage(Chat chat, Message message) { // Print out any messages we get back to standard out. System.out.println("Received message: " + message); } }); chat.sendMessage("Howdy!"); // Disconnect from the server con.disconnect();


XMPP기반 서버에 연결하기 위해 XMPPConnection을 사용하여 host name을 명시하고 connect하는 코드
혹시 IP, port base로 연결을 위해서는 ConnectionConfiguration을 사용해야 함.

 // Create a connection to the igniterealtime.org XMPP server.
 Connection con = new XMPPConnection("igniterealtime.org");
 // Connect to the server
 con.connect();


서버에서 Authentication이 필요하다면 계정 정보를 입력

 // Most servers require you to login before performing other tasks.
 con.login("jsmith", "mypass");


1:1 chatting에서는 상대방 계정(jdoe@igniterealtime.org)을 명시 필요.
Group chatting을 위해서는 MultiUserChat을 사용해야 함.. 관련 내용은 아래 링크를 참고

 // Start a new conversation with John Doe and send him a message.
 Chat chat = connection.getChatManager().createChat("jdoe@igniterealtime.org", new MessageListener() {
 
public void processMessage(Chat chat, Message message) { // Print out any messages we get back to standard out. System.out.println("Received message: " + message); } });



Message는 다음과 같은 Type을 가지고 각 Type별 mandatory, optional field는 다음과 같다. 

 Represents XMPP message packets. A message can be one of several types:
  • Message.Type.NORMAL -- (Default) a normal text message used in email like interface.
  • Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces.
  • Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats.
  • Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays.
  • Message.Type.ERROR -- indicates a messaging error.
For each message type, different message fields are typically used as follows:

 Message type
FieldNormalChatGroup ChatHeadlineXMPPError
subjectSHOULDSHOULD NOTSHOULD NOTSHOULD NOTSHOULD NOT
threadOPTIONALSHOULDOPTIONALOPTIONALSHOULD NOT
bodySHOULDSHOULDSHOULDSHOULDSHOULD NOT
errorMUST NOTMUST NOTMUST NOTMUST NOTMUST




뭐 message를 전달하고 connection을 close하는 코드...


  chat.sendMessage("Howdy!");
// Disconnect from the server con.disconnect();



사실 상 XMPP spec를 모르는 상태에서도 library를 사용하는데는 별 무리가 없을 정도로 쉽고 일관적이다.
Documentation이나 web 상에서 관련 질문이나 답변도 쉽게 찾을 수 있고..


아래는 작성해서 테스트 해본 샘플 코드...
Smack library에서 제공하는 것과 별 차이 없다.
* 다만 OpenFire의 default port는 5222


ChatRoom c1 = new ChatRoom("localhost", 5222);
c1.setAuthInfo("h@c-portable", "1234");
c1.talkWith("c@c-portable", new MessageListener() {
public void processMessage(Chat chat, Message message) {
System.out.print("h is received message => ");
if(message.getSubject() != null)
{
System.out.println(message.getSubject() + " : ");
}
System.out.println(message.getBody());
}
});

ChatRoom c2 = new ChatRoom("localhost", 5222);

c2.setAuthInfo("c@c-portable", "1234");
c2.talkWith("h@c-portable", new MessageListener() {
public void processMessage(Chat chat, Message message) {
System.out.print("c is received message => ");
if(message.getSubject() != null)
{
System.out.println(message.getSubject() + " : ");
}
System.out.println(message.getBody());
}
});




 class ChatRoom {
private Connection connection;
private Chat chat;
private String accountID;
private String accountPW;

ChatRoom(String host, int port) {
connection = new XMPPConnection(new ConnectionConfiguration(host, port));
}

void setAuthInfo(String id, String pw) {
accountID = id;
accountPW = pw;
}

void talkWith(String NameOfPersonTalkWith, MessageListener messageListener) {
try {
connection.connect();
connection.login(accountID, accountPW);

chat = connection.getChatManager().createChat(NameOfPersonTalkWith,
messageListener);
chat.sendMessage("Entered!");
} catch (XMPPException e) {
e.printStackTrace();
}

}

void send(String msg) {
if (chat != null) {
try {
chat.sendMessage(msg);
} catch (XMPPException e) {
e.printStackTrace();
}과
}
}

void close() {
connection.disconnect();
}

}

실행 결과
 h is received message => Entered!
c is received message => Entered!
h is received message => msg send 2
c is received message => msg send 3
h is received message => msg send 4
c is received message => msg send 5
h is received message => msg send 6
c is received message => msg send 7
h is received message => msg send 8
c is received message => msg send 9
h is received message => msg send 10