OSDN Git Service

8f84cdbe824025f9d35a61d6f72601b23acf7bec
[android-x86/packages-apps-IM.git] / src / com / android / im / imps / ImpsConnection.java
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      */
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      */
76     ImpsConnectionConfig getConfig() {
77         return mConfig;
78     }
79
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
101     void shutdown(){
102         shutdownOnError(null);
103     }
104
105     @Override
106     public int getCapability() {
107         return CAPABILITY_GROUP_CHAT | CAPABILITY_SESSION_REESTABLISHMENT;
108     }
109
110     @Override
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
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
154     public void networkTypeChanged() {
155         if (mCirChannel != null) {
156             mCirChannel.reconnect();
157         }
158     }
159
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
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
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
204         LoginTransaction() {
205             // We're not passing completion to ImpsAsyncTransaction. Instead
206             // we'll handle the notification in LoginTransaction.
207             super(mTransactionManager);
208         }
209
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
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
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
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
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
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
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
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
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
428     public ImpsSession getSession() {
429         return mSession;
430     }
431
432     @Override
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
443     public int[] getSupportedPresenceStatus() {
444         return mConfig.getPresenceMapping().getSupportedPresenceStatus();
445     }
446
447     public ImpsTransactionManager getTransactionManager() {
448         return mTransactionManager;
449     }
450
451     @Override
452     public ChatSessionManager getChatSessionManager() {
453         return mChatSessionManager;
454     }
455
456     @Override
457     public ContactListManager getContactListManager() {
458         return mContactListManager;
459     }
460
461     @Override
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      */
472     void sendPrimitive(Primitive primitive) {
473         mDataChannel.sendPrimitive(primitive);
474     }
475
476     /**
477      * Sends a PollingRequest to the server.
478      */
479     void sendPollingRequest() {
480         Primitive pollingRequest = new Primitive(ImpsTags.Polling_Request);
481         pollingRequest.setSession(getSession().getID());
482         mDataChannel.sendPrimitive(pollingRequest);
483     }
484
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
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
534         public PrimitiveDispatcherThread(DataChannel channel)
535         {
536             super("ImpsPrimitiveDispatcher");
537             mChannel = channel;
538         }
539
540         public void changeDataChannel(DataChannel channel) {
541             mChannel = channel;
542             interrupt();
543         }
544
545         @Override
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
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      */
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
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
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
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
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
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
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
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
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
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 }