1 /* 2 * Copyright (C) 2007-2008 Esmertec AG. 3 * Copyright (C) 2007-2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.im.imps; 19 20 import java.util.ArrayList; 21 import java.util.HashMap; 22 import java.util.Map; 23 24 import com.android.im.engine.ChatGroupManager; 25 import com.android.im.engine.ChatSessionManager; 26 import com.android.im.engine.Contact; 27 import com.android.im.engine.ContactListManager; 28 import com.android.im.engine.ImConnection; 29 import com.android.im.engine.ImErrorInfo; 30 import com.android.im.engine.ImException; 31 import com.android.im.engine.LoginInfo; 32 import com.android.im.engine.Presence; 33 import com.android.im.imps.ImpsConnectionConfig.CirMethod; 34 import com.android.im.imps.ImpsConnectionConfig.TransportType; 35 import com.android.im.imps.Primitive.TransactionMode; 36 37 /** 38 * An implementation of ImConnection of Wireless Village IMPS protocol. 39 */ 40 public class ImpsConnection extends ImConnection { 41 ImpsConnectionConfig mConfig; 42 43 DataChannel mDataChannel; 44 private CirChannel mCirChannel; 45 private PrimitiveDispatcherThread mDispatcherThread; 46 47 ImpsSession mSession; 48 ImpsTransactionManager mTransactionManager; 49 private ImpsChatSessionManager mChatSessionManager; 50 private ImpsContactListManager mContactListManager; 51 private ImpsChatGroupManager mChatGroupManager; 52 private boolean mReestablishing; 53 54 /** 55 * Constructs a new WVConnection with a WVConnectionConfig object. 56 * 57 * @param config the configuration. 58 * @throws ImException if there's an error in the configuration. 59 */ ImpsConnection(ImpsConnectionConfig config)60 public ImpsConnection(ImpsConnectionConfig config) { 61 super(); 62 63 mConfig = config; 64 65 mTransactionManager = new ImpsTransactionManager(this); 66 mChatSessionManager = new ImpsChatSessionManager(this); 67 mContactListManager = new ImpsContactListManager(this); 68 mChatGroupManager = new ImpsChatGroupManager(this); 69 } 70 71 /** 72 * Gets the configuration of this connection. 73 * 74 * @return the configuration. 75 */ getConfig()76 ImpsConnectionConfig getConfig() { 77 return mConfig; 78 } 79 shutdownOnError(ImErrorInfo error)80 synchronized void shutdownOnError(ImErrorInfo error) { 81 if(mState == DISCONNECTED) { 82 return; 83 } 84 85 if (mCirChannel != null) { 86 mCirChannel.shutdown(); 87 } 88 if (mDispatcherThread != null) { 89 mDispatcherThread.shutdown(); 90 } 91 if (mDataChannel != null) { 92 mDataChannel.shutdown(); 93 } 94 if (mContactListManager != null && !mReestablishing) { 95 mContactListManager.reset(); 96 } 97 setState(mReestablishing ? SUSPENDED: DISCONNECTED, error); 98 mReestablishing = false; 99 } 100 shutdown()101 void shutdown(){ 102 shutdownOnError(null); 103 } 104 105 @Override getCapability()106 public int getCapability() { 107 return CAPABILITY_GROUP_CHAT | CAPABILITY_SESSION_REESTABLISHMENT; 108 } 109 110 @Override loginAsync(LoginInfo loginInfo)111 public void loginAsync(LoginInfo loginInfo) { 112 if (!checkAndSetState(DISCONNECTED)) { 113 return; 114 } 115 try { 116 mSession = new ImpsSession(this, loginInfo); 117 } catch (ImException e) { 118 setState(DISCONNECTED, e.getImError()); 119 return; 120 } 121 doLogin(); 122 } 123 124 @Override reestablishSessionAsync( HashMap<String, String> cookie)125 public void reestablishSessionAsync( 126 HashMap<String, String> cookie) { 127 if (!checkAndSetState(SUSPENDED)) { 128 return; 129 } 130 // If we can resume from the data channel, which means the 131 // session is still valid, we can just re-use the existing 132 // session and don't need to re-establish it. 133 if (mDataChannel.resume()) { 134 try { 135 setupCIRChannel(); 136 } catch(ImException e) {} 137 setState(LOGGED_IN, null); 138 } else { 139 // Failed to resume the data channel which means the 140 // session might have expired, we need to re-establish 141 // the session by signing in again. 142 mReestablishing = true; 143 try { 144 mSession = new ImpsSession(this, cookie); 145 } catch (ImException e) { 146 setState(DISCONNECTED, e.getImError()); 147 return; 148 } 149 doLogin(); 150 } 151 } 152 153 @Override networkTypeChanged()154 public void networkTypeChanged() { 155 if (mCirChannel != null) { 156 mCirChannel.reconnect(); 157 } 158 } 159 checkAndSetState(int state)160 private synchronized boolean checkAndSetState(int state) { 161 if(mState != state){ 162 return false; 163 } 164 setState(LOGGING_IN, null); 165 return true; 166 } 167 doLogin()168 private void doLogin() { 169 try { 170 if (mConfig.useSmsAuth()) { 171 mDataChannel = new SmsDataChannel(this); 172 } else { 173 mDataChannel = createDataChannel(); 174 } 175 mDataChannel.connect(); 176 } catch (ImException e) { 177 ImErrorInfo error = e.getImError(); 178 if(error == null){ 179 error = new ImErrorInfo(ImErrorInfo.UNKNOWN_LOGIN_ERROR, 180 e.getMessage()); 181 } 182 shutdownOnError(error); 183 return; 184 } 185 186 mDispatcherThread = new PrimitiveDispatcherThread(mDataChannel); 187 mDispatcherThread.start(); 188 189 LoginTransaction login = new LoginTransaction(); 190 login.startAuthenticate(); 191 } 192 193 @Override getSessionContext()194 public HashMap<String, String> getSessionContext() { 195 if(mState != LOGGED_IN) { 196 return null; 197 } else { 198 return mSession.getContext(); 199 } 200 } 201 202 class LoginTransaction extends MultiPhaseTransaction { 203 LoginTransaction()204 LoginTransaction() { 205 // We're not passing completion to ImpsAsyncTransaction. Instead 206 // we'll handle the notification in LoginTransaction. 207 super(mTransactionManager); 208 } 209 startAuthenticate()210 public void startAuthenticate() { 211 Primitive login = buildBasicLoginReq(); 212 if (mConfig.use4wayLogin()) { 213 // first login request of 4 way login 214 String[] supportedDigestSchema = mConfig.getPasswordDigest().getSupportedDigestSchema(); 215 for (String element : supportedDigestSchema) { 216 login.addElement(ImpsTags.DigestSchema, element); 217 } 218 } else { 219 // 2 way login 220 login.addElement(ImpsTags.Password, mSession.getPassword()); 221 } 222 sendRequest(login); 223 } 224 225 @Override processResponse(Primitive response)226 public TransactionStatus processResponse(Primitive response) { 227 if (response.getElement(ImpsTags.SessionID) != null) { 228 // If server chooses authentication based on network, we might 229 // got the final Login-Response before the 2nd Login-Request. 230 String sessionId = response.getElementContents(ImpsTags.SessionID); 231 String keepAliveTime = response.getElementContents(ImpsTags.KeepAliveTime); 232 String capablityReqeust = response.getElementContents(ImpsTags.CapabilityRequest); 233 234 long keepAlive = ImpsUtils.parseLong(keepAliveTime, 235 mConfig.getDefaultKeepAliveInterval()); 236 // make sure we always have time to send keep-alive requests. 237 // see buildBasicLoginReq(). 238 keepAlive -= 5; 239 mSession.setId(sessionId); 240 mSession.setKeepAliveTime(keepAlive); 241 mSession.setCapablityRequestRequired(ImpsUtils.isTrue(capablityReqeust)); 242 243 onAuthenticated(); 244 return TransactionStatus.TRANSACTION_COMPLETED; 245 } else { 246 return sendSecondLogin(response); 247 } 248 } 249 250 @Override processResponseError(ImpsErrorInfo error)251 public TransactionStatus processResponseError(ImpsErrorInfo error) { 252 if (error.getCode() == ImpsConstants.STATUS_UNAUTHORIZED 253 && error.getPrimitive() != null) { 254 if (mConfig.use4wayLogin()) { 255 // Not really an error. Send the 2nd Login-Request. 256 return sendSecondLogin(error.getPrimitive()); 257 } else { 258 // We have already sent password in 2way login, while OZ's 259 // yahoo gateway server returns "401 - Further authorization 260 // required" instead of "409 - Invalid password" if the 261 // password only contains spaces. 262 shutdownOnError(new ImErrorInfo(409, "Invalid password")); 263 return TransactionStatus.TRANSACTION_COMPLETED; 264 } 265 } else if(error.getCode() == ImpsConstants.STATUS_COULD_NOT_RECOVER_SESSION) { 266 // The server could not recover the session, create a new 267 // session and try to login again. 268 LoginInfo loginInfo = mSession.getLoginInfo(); 269 try { 270 mSession = new ImpsSession(ImpsConnection.this, loginInfo); 271 } catch (ImException ignore) { 272 // This shouldn't happen since we have tried to login with 273 // the loginInfo 274 } 275 startAuthenticate(); 276 return TransactionStatus.TRANSACTION_COMPLETED; 277 } else { 278 shutdownOnError(error); 279 return TransactionStatus.TRANSACTION_COMPLETED; 280 } 281 } 282 sendSecondLogin(Primitive res)283 private TransactionStatus sendSecondLogin(Primitive res) { 284 try { 285 Primitive secondLogin = buildBasicLoginReq(); 286 287 String nonce = res.getElementContents(ImpsTags.Nonce); 288 String digestSchema = res.getElementContents(ImpsTags.DigestSchema); 289 String digestBytes = mConfig.getPasswordDigest().digest(digestSchema, nonce, 290 mSession.getPassword()); 291 292 secondLogin.addElement(ImpsTags.DigestBytes, digestBytes); 293 294 sendRequest(secondLogin); 295 return TransactionStatus.TRANSACTION_CONTINUE; 296 } catch (ImException e) { 297 ImpsLog.logError(e); 298 shutdownOnError(new ImErrorInfo(ImErrorInfo.UNKNOWN_ERROR, e.toString())); 299 return TransactionStatus.TRANSACTION_COMPLETED; 300 } 301 } 302 onAuthenticated()303 private void onAuthenticated() { 304 // The user has chosen logout before the session established, just 305 // send the Logout-Request in this case. 306 if (mState == LOGGING_OUT) { 307 sendLogoutRequest(); 308 return; 309 } 310 311 if (mConfig.useSmsAuth() 312 && mConfig.getDataChannelBinding() != TransportType.SMS) { 313 // SMS data channel was used if it's set to send authentication 314 // over SMS. Switch to the config data channel after authentication 315 // completed. 316 try { 317 DataChannel dataChannel = createDataChannel(); 318 dataChannel.connect(); 319 320 mDataChannel.shutdown(); 321 mDataChannel = dataChannel; 322 mDispatcherThread.changeDataChannel(dataChannel); 323 } catch (ImException e) { 324 // This should not happen since only http data channel which 325 // does not do the real network connection in connect() is 326 // valid here now. 327 logoutAsync(); 328 return; 329 } 330 } 331 332 if(mSession.isCapablityRequestRequired()) { 333 mSession.negotiateCapabilityAsync(new AsyncCompletion(){ 334 public void onComplete() { 335 onCapabilityNegotiated(); 336 } 337 338 public void onError(ImErrorInfo error) { 339 shutdownOnError(error); 340 } 341 }); 342 } else { 343 onCapabilityNegotiated(); 344 } 345 } 346 onCapabilityNegotiated()347 void onCapabilityNegotiated() { 348 mDataChannel.setServerMinPoll(mSession.getServerPollMin()); 349 if(getConfig().getCirChannelBinding() != CirMethod.NONE) { 350 try { 351 setupCIRChannel(); 352 } catch (ImException e) { 353 shutdownOnError(new ImErrorInfo( 354 ImErrorInfo.UNSUPPORTED_CIR_CHANNEL, e.toString())); 355 return; 356 } 357 } 358 359 mSession.negotiateServiceAsync(new AsyncCompletion(){ 360 public void onComplete() { 361 onServiceNegotiated(); 362 } 363 364 public void onError(ImErrorInfo error) { 365 shutdownOnError(error); 366 } 367 }); 368 } 369 onServiceNegotiated()370 void onServiceNegotiated() { 371 mDataChannel.startKeepAlive(mSession.getKeepAliveTime()); 372 373 retrieveUserPresenceAsync(new AsyncCompletion() { 374 public void onComplete() { 375 setState(LOGGED_IN, null); 376 if (mReestablishing) { 377 ImpsContactListManager listMgr= (ImpsContactListManager) getContactListManager(); 378 listMgr.subscribeToAllListAsync(); 379 mReestablishing = false; 380 } 381 } 382 383 public void onError(ImErrorInfo error) { 384 // Just continue. initUserPresenceAsync already made a 385 // default mUserPresence for us. 386 onComplete(); 387 } 388 }); 389 } 390 } 391 392 @Override logoutAsync()393 public void logoutAsync() { 394 setState(LOGGING_OUT, null); 395 // Shutdown the CIR channel first. 396 if(mCirChannel != null) { 397 mCirChannel.shutdown(); 398 mCirChannel = null; 399 } 400 401 // Only send the Logout-Request if the session has been established. 402 if (mSession.getID() != null) { 403 sendLogoutRequest(); 404 } 405 } 406 sendLogoutRequest()407 void sendLogoutRequest() { 408 // We cannot shut down our connections in ImpsAsyncTransaction.onResponse() 409 // because at that time the logout transaction itself hasn't ended yet. So 410 // we have to do this in this completion object. 411 AsyncCompletion completion = new AsyncCompletion() { 412 public void onComplete() { 413 shutdown(); 414 } 415 416 public void onError(ImErrorInfo error) { 417 // We simply ignore all errors when logging out. 418 // NowIMP responds a <Disconnect> instead of <Status> on logout request. 419 shutdown(); 420 } 421 }; 422 AsyncTransaction tx = new SimpleAsyncTransaction(mTransactionManager, 423 completion); 424 Primitive logoutPrimitive = new Primitive(ImpsTags.Logout_Request); 425 tx.sendRequest(logoutPrimitive); 426 } 427 getSession()428 public ImpsSession getSession() { 429 return mSession; 430 } 431 432 @Override getLoginUser()433 public Contact getLoginUser() { 434 if(mSession == null){ 435 return null; 436 } 437 Contact loginUser = mSession.getLoginUser(); 438 loginUser.setPresence(getUserPresence()); 439 return loginUser; 440 } 441 442 @Override getSupportedPresenceStatus()443 public int[] getSupportedPresenceStatus() { 444 return mConfig.getPresenceMapping().getSupportedPresenceStatus(); 445 } 446 getTransactionManager()447 public ImpsTransactionManager getTransactionManager() { 448 return mTransactionManager; 449 } 450 451 @Override getChatSessionManager()452 public ChatSessionManager getChatSessionManager() { 453 return mChatSessionManager; 454 } 455 456 @Override getContactListManager()457 public ContactListManager getContactListManager() { 458 return mContactListManager; 459 } 460 461 @Override getChatGroupManager()462 public ChatGroupManager getChatGroupManager() { 463 return mChatGroupManager; 464 } 465 466 /** 467 * Sends a specific primitive to the server. It will return immediately 468 * after the primitive has been put to the sending queue. 469 * 470 * @param primitive the packet to send. 471 */ sendPrimitive(Primitive primitive)472 void sendPrimitive(Primitive primitive) { 473 mDataChannel.sendPrimitive(primitive); 474 } 475 476 /** 477 * Sends a PollingRequest to the server. 478 */ sendPollingRequest()479 void sendPollingRequest() { 480 Primitive pollingRequest = new Primitive(ImpsTags.Polling_Request); 481 pollingRequest.setSession(getSession().getID()); 482 mDataChannel.sendPrimitive(pollingRequest); 483 } 484 createDataChannel()485 private DataChannel createDataChannel() throws ImException { 486 TransportType dataChannelBinding = mConfig.getDataChannelBinding(); 487 if (dataChannelBinding == TransportType.HTTP) { 488 return new HttpDataChannel(this); 489 } else if (dataChannelBinding == TransportType.SMS) { 490 return new SmsDataChannel(this); 491 } else { 492 throw new ImException("Unsupported data channel binding"); 493 } 494 } 495 setupCIRChannel()496 void setupCIRChannel() throws ImException { 497 if(mConfig.getDataChannelBinding() == TransportType.SMS) { 498 // No CIR channel is needed, do nothing. 499 return; 500 } 501 CirMethod cirMethod = mSession.getCurrentCirMethod(); 502 if (cirMethod == null) { 503 cirMethod = mConfig.getCirChannelBinding(); 504 505 if (!mSession.getSupportedCirMethods().contains(cirMethod)) { 506 // Sever don't support the CIR method 507 cirMethod = CirMethod.SHTTP; 508 } 509 mSession.setCurrentCirMethod(cirMethod); 510 } 511 512 if (cirMethod == CirMethod.SHTTP) { 513 mCirChannel = new HttpCirChannel(this, mDataChannel); 514 } else if (cirMethod == CirMethod.STCP) { 515 mCirChannel = new TcpCirChannel(this); 516 } else if (cirMethod == CirMethod.SSMS) { 517 mCirChannel = new SmsCirChannel(this); 518 } else if (cirMethod == CirMethod.NONE) { 519 //Do nothing 520 } else { 521 throw new ImException(ImErrorInfo.UNSUPPORTED_CIR_CHANNEL, 522 "Unsupported CIR channel binding"); 523 } 524 525 if(mCirChannel != null) { 526 mCirChannel.connect(); 527 } 528 } 529 530 private class PrimitiveDispatcherThread extends Thread { 531 private boolean stopped; 532 private DataChannel mChannel; 533 PrimitiveDispatcherThread(DataChannel channel)534 public PrimitiveDispatcherThread(DataChannel channel) 535 { 536 super("ImpsPrimitiveDispatcher"); 537 mChannel = channel; 538 } 539 changeDataChannel(DataChannel channel)540 public void changeDataChannel(DataChannel channel) { 541 mChannel = channel; 542 interrupt(); 543 } 544 545 @Override run()546 public void run() { 547 Primitive primitive = null; 548 while (!stopped) { 549 try { 550 primitive = mChannel.receivePrimitive(); 551 } catch (InterruptedException e) { 552 if (stopped) { 553 break; 554 } 555 primitive = null; 556 } 557 558 if (primitive != null) { 559 try { 560 processIncomingPrimitive(primitive); 561 } catch (Throwable t) { 562 // We don't know what is going to happen in the various 563 // listeners. 564 ImpsLog.logError("ImpsDispatcher: uncaught Throwable", t); 565 } 566 } 567 } 568 } 569 shutdown()570 void shutdown() { 571 stopped = true; 572 interrupt(); 573 } 574 } 575 576 /** 577 * Handles the primitive received from the server. 578 * 579 * @param primitive the received primitive. 580 */ processIncomingPrimitive(Primitive primitive)581 void processIncomingPrimitive(Primitive primitive) { 582 // if CIR is 'F', the CIR channel is not available. Re-establish it. 583 if (primitive.getCir() != null && ImpsUtils.isFalse(primitive.getCir())) { 584 if(mCirChannel != null) { 585 mCirChannel.shutdown(); 586 } 587 try { 588 setupCIRChannel(); 589 } catch (ImException e) { 590 e.printStackTrace(); 591 } 592 } 593 594 if (primitive.getPoll() != null && ImpsUtils.isTrue(primitive.getPoll())) { 595 sendPollingRequest(); 596 } 597 598 if (primitive.getType().equals(ImpsTags.Disconnect)) { 599 if (mState != LOGGING_OUT) { 600 ImErrorInfo error = ImpsUtils.checkResultError(primitive); 601 shutdownOnError(error); 602 return; 603 } 604 } 605 606 if (primitive.getTransactionMode() == TransactionMode.Response) { 607 ImpsErrorInfo error = ImpsUtils.checkResultError(primitive); 608 if (error != null) { 609 int code = error.getCode(); 610 if (code == ImpsErrorInfo.SESSION_EXPIRED 611 || code == ImpsErrorInfo.FORCED_LOGOUT 612 || code == ImpsErrorInfo.INVALID_SESSION) { 613 shutdownOnError(error); 614 return; 615 } 616 } 617 } 618 619 // According to the IMPS spec, only VersionDiscoveryResponse which 620 // are not supported now doesn't have a transaction ID. 621 if (primitive.getTransactionID() != null) { 622 mTransactionManager.notifyIncomingPrimitive(primitive); 623 } 624 } 625 626 @Override doUpdateUserPresenceAsync(Presence presence)627 protected void doUpdateUserPresenceAsync(Presence presence) { 628 ArrayList<PrimitiveElement> presenceSubList = ImpsPresenceUtils.buildUpdatePresenceElems( 629 mUserPresence, presence, mConfig.getPresenceMapping()); 630 Primitive request = buildUpdatePresenceReq(presenceSubList); 631 // Need to make a copy because the presence passed in may change 632 // before the transaction finishes. 633 final Presence newPresence = new Presence(presence); 634 635 AsyncTransaction tx = new AsyncTransaction(mTransactionManager) { 636 637 @Override 638 public void onResponseOk(Primitive response) { 639 savePresenceChange(newPresence); 640 notifyUserPresenceUpdated(); 641 } 642 643 @Override 644 public void onResponseError(ImpsErrorInfo error) { 645 notifyUpdateUserPresenceError(error); 646 } 647 }; 648 tx.sendRequest(request); 649 } 650 savePresenceChange(Presence newPresence)651 void savePresenceChange(Presence newPresence) { 652 mUserPresence.setStatusText(newPresence.getStatusText()); 653 mUserPresence.setStatus(newPresence.getStatus()); 654 mUserPresence.setAvatar(newPresence.getAvatarData(), newPresence.getAvatarType()); 655 // no need to update extended info because it's always read only. 656 } 657 retrieveUserPresenceAsync(final AsyncCompletion completion)658 void retrieveUserPresenceAsync(final AsyncCompletion completion) { 659 Primitive request = new Primitive(ImpsTags.GetPresence_Request); 660 661 request.addElement(this.getSession().getLoginUserAddress().toPrimitiveElement()); 662 AsyncTransaction tx = new AsyncTransaction(mTransactionManager){ 663 664 @Override 665 public void onResponseOk(Primitive response) { 666 PrimitiveElement presence = response.getElement(ImpsTags.Presence); 667 PrimitiveElement presenceSubList = presence.getChild(ImpsTags.PresenceSubList); 668 mUserPresence = ImpsPresenceUtils.extractPresence(presenceSubList, 669 mConfig.getPresenceMapping()); 670 // XXX: workaround for the OZ IMPS GTalk server that 671 // returns an initial 'F' OnlineStatus. Set the online 672 // status to available in this case. 673 if(mUserPresence.getStatus() == Presence.OFFLINE) { 674 mUserPresence.setStatus(Presence.AVAILABLE); 675 } 676 compareAndUpdateClientInfo(); 677 } 678 679 @Override 680 public void onResponseError(ImpsErrorInfo error) { 681 mUserPresence = new Presence(Presence.AVAILABLE, "", null, 682 null, Presence.CLIENT_TYPE_MOBILE, ImpsUtils.getClientInfo()); 683 completion.onError(error); 684 } 685 686 private void compareAndUpdateClientInfo() { 687 if (!ImpsUtils.getClientInfo().equals(mUserPresence.getExtendedInfo())) { 688 updateClientInfoAsync(completion); 689 return; 690 } 691 // no need to update our client info to the server again 692 completion.onComplete(); 693 } 694 }; 695 696 tx.sendRequest(request); 697 } 698 updateClientInfoAsync(AsyncCompletion completion)699 void updateClientInfoAsync(AsyncCompletion completion) { 700 Primitive updatePresenceRequest = buildUpdatePresenceReq(buildClientInfoElem()); 701 702 AsyncTransaction tx = new SimpleAsyncTransaction(mTransactionManager, 703 completion); 704 tx.sendRequest(updatePresenceRequest); 705 } 706 buildUpdatePresenceReq(PrimitiveElement presence)707 private Primitive buildUpdatePresenceReq(PrimitiveElement presence) { 708 ArrayList<PrimitiveElement> presences = new ArrayList<PrimitiveElement>(); 709 710 presences.add(presence); 711 712 return buildUpdatePresenceReq(presences); 713 } 714 buildUpdatePresenceReq(ArrayList<PrimitiveElement> presences)715 private Primitive buildUpdatePresenceReq(ArrayList<PrimitiveElement> presences) { 716 Primitive updatePresenceRequest = new Primitive(ImpsTags.UpdatePresence_Request); 717 718 PrimitiveElement presenceSubList = updatePresenceRequest 719 .addElement(ImpsTags.PresenceSubList); 720 presenceSubList.setAttribute(ImpsTags.XMLNS, mConfig.getPresenceNs()); 721 722 for (PrimitiveElement presence : presences) { 723 presenceSubList.addChild(presence); 724 } 725 726 return updatePresenceRequest; 727 } 728 buildClientInfoElem()729 private PrimitiveElement buildClientInfoElem() { 730 PrimitiveElement clientInfo = new PrimitiveElement(ImpsTags.ClientInfo); 731 clientInfo.addChild(ImpsTags.Qualifier, true); 732 733 Map<String, String> map = ImpsUtils.getClientInfo(); 734 for (Map.Entry<String, String> item : map.entrySet()) { 735 clientInfo.addChild(item.getKey(), item.getValue()); 736 } 737 738 return clientInfo; 739 } 740 buildBasicLoginReq()741 Primitive buildBasicLoginReq() { 742 Primitive login = new Primitive(ImpsTags.Login_Request); 743 login.addElement(ImpsTags.UserID, mSession.getUserName()); 744 PrimitiveElement clientId = login.addElement(ImpsTags.ClientID); 745 clientId.addChild(ImpsTags.URL, mConfig.getClientId()); 746 if (mConfig.getMsisdn() != null) { 747 clientId.addChild(ImpsTags.MSISDN, mConfig.getMsisdn()); 748 } 749 // we request for a bigger TimeToLive value than our default keep 750 // alive interval to make sure we always have time to send the keep 751 // alive requests. 752 login.addElement(ImpsTags.TimeToLive, 753 Integer.toString(mConfig.getDefaultKeepAliveInterval() + 5)); 754 login.addElement(ImpsTags.SessionCookie, mSession.getCookie()); 755 return login; 756 } 757 758 @Override suspend()759 synchronized public void suspend() { 760 setState(SUSPENDING, null); 761 762 if (mCirChannel != null) { 763 mCirChannel.shutdown(); 764 } 765 766 if (mDataChannel != null) { 767 mDataChannel.suspend(); 768 } 769 770 setState(SUSPENDED, null); 771 } 772 } 773