1 /* 2 * Copyright (C) 2007 Esmertec AG. 3 * Copyright (C) 2007 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.List; 23 24 import com.android.im.engine.Contact; 25 import com.android.im.engine.ImErrorInfo; 26 import com.android.im.engine.ImException; 27 import com.android.im.engine.LoginInfo; 28 import com.android.im.imps.ImpsConnectionConfig.CirMethod; 29 import com.android.im.imps.ImpsConnectionConfig.TransportType; 30 31 /** 32 * Represents the context of an IMPS session. The IMPS session is a framework in 33 * which the IMPS services are provided to the IMPS client. It's established 34 * when the client logs in and terminated when either the client logs out or the 35 * SAP decides to disconnect the session. 36 */ 37 public class ImpsSession { 38 private static final String KEY_CIR_HTTP_ADDRESS = "cirHttpAddress"; 39 private static final String KEY_CIR_TCP_PORT = "cirTcpPort"; 40 private static final String KEY_CIR_TCP_ADDRESS = "cirTcpAddress"; 41 private static final String KEY_CIR_METHOD = "CirMethod"; 42 private static final String KEY_SERVER_POLL_MIN = "serverPollMin"; 43 private static final String KEY_PASSWORD = "password"; 44 private static final String KEY_USERNAME = "username"; 45 private static final String KEY_KEEP_ALIVE_TIME = "keepAliveTime"; 46 private static final String KEY_SESSION_COOKIE = "sessionCookie"; 47 private static final String KEY_SESSION_ID = "sessionId"; 48 49 private static final int DEFAULT_TCP_PORT = 3171; 50 51 private ImpsConnection mConnection; 52 private String mId; 53 private String mCookie; 54 private long mKeepAliveTime; 55 private CirMethod mCurrentCirMethod; 56 private String mCirTcpAddress; 57 private int mCirTcpPort = DEFAULT_TCP_PORT; 58 private long mServerPollMin; 59 private String mCirHttpAddress; 60 private LoginInfo mLoginInfo; 61 62 private boolean mCapablityRequest; 63 private List<CirMethod> mSupportedCirMethod; 64 65 private Contact mLoginUser; 66 67 PrimitiveElement mServiceTree; 68 69 /** 70 * Flag that indicates this is a new created session or not. 71 */ 72 private boolean mNew; 73 74 ImpsSession(ImpsConnection connection, LoginInfo info) throws ImException{ 75 mConnection = connection; 76 setLoginInfo(info); 77 78 mNew = true; 79 mCookie = ImpsUtils.genSessionCookie(); 80 81 mCirTcpPort = DEFAULT_TCP_PORT; 82 mServerPollMin = connection.getConfig().getDefaultServerPollMin(); 83 } 84 85 ImpsSession(ImpsConnection connection, HashMap<String, String> values) 86 throws ImException { 87 mConnection = connection; 88 mNew = false; 89 mId = values.get(KEY_SESSION_ID); 90 if (mId == null || mId.length() == 0) { 91 throw new ImException(ImErrorInfo.INVALID_SESSION_CONTEXT, 92 "Missing session id"); 93 } 94 mCookie = values.get(KEY_SESSION_COOKIE); 95 if (mCookie == null || mCookie.length() == 0) { 96 throw new ImException(ImErrorInfo.INVALID_SESSION_CONTEXT, 97 "Missing session cookie"); 98 } 99 try { 100 mKeepAliveTime = Long.parseLong(values.get(KEY_KEEP_ALIVE_TIME)); 101 } catch (NumberFormatException e) { 102 throw new ImException(ImErrorInfo.INVALID_SESSION_CONTEXT, 103 "Invalid keepAliveTime"); 104 } 105 try { 106 mServerPollMin = Long.parseLong(values.get(KEY_SERVER_POLL_MIN)); 107 } catch (NumberFormatException e) { 108 throw new ImException(ImErrorInfo.INVALID_SESSION_CONTEXT, 109 "Invalid serverPollMin"); 110 } 111 String username = values.get(KEY_USERNAME); 112 String password = values.get(KEY_PASSWORD); 113 // Empty password might be valid 114 if (username == null || username.length() == 0 || password == null) { 115 throw new ImException(ImErrorInfo.INVALID_SESSION_CONTEXT, 116 "Invalid username or password"); 117 } 118 setLoginInfo(new LoginInfo(username, password)); 119 120 mCurrentCirMethod = CirMethod.valueOf(values.get(KEY_CIR_METHOD)); 121 if (mCurrentCirMethod == CirMethod.STCP) { 122 mCirTcpAddress = values.get(KEY_CIR_TCP_ADDRESS); 123 if (mCirTcpAddress == null || mCirTcpAddress.length() == 0) { 124 throw new ImException(ImErrorInfo.INVALID_SESSION_CONTEXT, 125 "Missing CirTcpAddress"); 126 } 127 try { 128 mCirTcpPort = Integer.parseInt(values.get(KEY_CIR_TCP_PORT)); 129 } catch (NumberFormatException e) { 130 throw new ImException(ImErrorInfo.INVALID_SESSION_CONTEXT, 131 "Invalid CirTcpPort"); 132 } 133 } else if (mCurrentCirMethod == CirMethod.SHTTP) { 134 mCirHttpAddress = values.get(KEY_CIR_HTTP_ADDRESS); 135 } 136 } 137 138 public HashMap<String, String> getContext() { 139 HashMap<String, String> values = new HashMap<String, String>(); 140 141 values.put(KEY_SESSION_ID, mId); 142 values.put(KEY_SESSION_COOKIE, mCookie); 143 values.put(KEY_KEEP_ALIVE_TIME, Long.toString(mKeepAliveTime)); 144 values.put(KEY_USERNAME, mLoginInfo.getUserName()); 145 values.put(KEY_PASSWORD, mLoginInfo.getPassword()); 146 values.put(KEY_SERVER_POLL_MIN, Long.toString(mServerPollMin)); 147 148 values.put(KEY_CIR_METHOD, mCurrentCirMethod.name()); 149 if(mCurrentCirMethod == CirMethod.STCP) { 150 values.put(KEY_CIR_TCP_ADDRESS, mCirTcpAddress); 151 values.put(KEY_CIR_TCP_PORT, Integer.toString(mCirTcpPort)); 152 } else if (mCurrentCirMethod == CirMethod.SHTTP) { 153 values.put(KEY_CIR_HTTP_ADDRESS, mCirHttpAddress); 154 } 155 return values; 156 } 157 158 /** 159 * Gets the unique id of the session. 160 * 161 * @return the unique id of the session. 162 */ 163 public String getID() { 164 return mId; 165 } 166 167 public void setId(String id) { 168 mId = id; 169 } 170 171 public String getCookie() { 172 return mCookie; 173 } 174 175 public String getUserName() { 176 return mLoginInfo.getUserName(); 177 } 178 179 public String getPassword() { 180 return mLoginInfo.getPassword(); 181 } 182 183 public LoginInfo getLoginInfo() { 184 return mLoginInfo; 185 } 186 /** 187 * Gets the auto logout timer value. 188 * 189 * @return the auto logout timer value. 190 */ 191 public long getKeepAliveTime() { 192 return mKeepAliveTime; 193 } 194 195 public void setKeepAliveTime(long keepAliveTime) { 196 mKeepAliveTime = keepAliveTime; 197 } 198 199 /** 200 * Gets if further capability request is required in the session. 201 * 202 * @return <code>true</code> if further capability request is required. 203 */ 204 public boolean isCapablityRequestRequired() { 205 return mCapablityRequest || mNew; 206 } 207 208 public void setCapablityRequestRequired(boolean required) { 209 mCapablityRequest = required; 210 } 211 212 public ImpsUserAddress getLoginUserAddress() { 213 return (ImpsUserAddress) mLoginUser.getAddress(); 214 } 215 216 public Contact getLoginUser() { 217 return mLoginUser; 218 } 219 220 /** 221 * Sets the Login information. After login successfully, the login 222 * information should be saved in the session context so that we can auto 223 * login when reconnect to the server. 224 * 225 * @param loginInfo the login information. 226 * @throws ImException 227 */ 228 private void setLoginInfo(LoginInfo loginInfo) throws ImException { 229 try { 230 ImpsAddress address = new ImpsUserAddress(loginInfo.getUserName()); 231 mLoginUser = new Contact(address, address.getScreenName()); 232 mLoginInfo = loginInfo; 233 } catch (IllegalArgumentException e) { 234 throw new ImException(ImErrorInfo.INVALID_USERNAME, 235 "Invalid username"); 236 } 237 } 238 239 /** 240 * Gets a collection of CIR methods that are supported by both the client 241 * and the server. 242 * 243 * @return a collection of supported CIR methods 244 */ 245 public List<CirMethod> getSupportedCirMethods() { 246 return mSupportedCirMethod; 247 } 248 249 public CirMethod getCurrentCirMethod() { 250 return mCurrentCirMethod; 251 } 252 253 public void setCurrentCirMethod(CirMethod cirMethod) { 254 mCurrentCirMethod = cirMethod; 255 } 256 257 /** 258 * Gets the IP address for standalone TCP/IP CIR method. 259 * 260 * @return the IP address for standalone TCP/IP CIR method 261 */ 262 public String getCirTcpAddress() { 263 return mCirTcpAddress; 264 } 265 266 /** 267 * Gets the port number for the standalone TCP/IP CIR method. 268 * 269 * @return the port number for the standalone TCP/IP CIR method. 270 */ 271 public int getCirTcpPort() { 272 return mCirTcpPort; 273 } 274 275 /** 276 * Gets the minimum time interval (in seconds) that MUST pass before two 277 * subsequent PollingRequest transactions. 278 * 279 * @return the minimum time interval in seconds. 280 */ 281 public long getServerPollMin() { 282 return mServerPollMin; 283 } 284 285 /** 286 * Gets the URL used for standalone HTTP binding of CIR channel. 287 * 288 * @return the URL. 289 */ 290 public String getCirHttpAddress() { 291 return mCirHttpAddress; 292 } 293 294 /** 295 * Gets the service tree of the features and functions that the server 296 * supports. 297 * 298 * @return the service tree. 299 */ 300 public PrimitiveElement getServiceTree() { 301 return mServiceTree; 302 } 303 304 /** 305 * Perform client capability negotiation with the server asynchronously. 306 * 307 * @param completion Async completion object. 308 */ 309 public void negotiateCapabilityAsync(AsyncCompletion completion) { 310 Primitive capabilityRequest = buildCapabilityRequest(); 311 312 AsyncTransaction tx = new AsyncTransaction( 313 mConnection.getTransactionManager(), completion) { 314 315 @Override 316 public void onResponseOk(Primitive response) { 317 extractCapability(response); 318 } 319 320 @Override 321 public void onResponseError(ImpsErrorInfo error) { } 322 }; 323 324 tx.sendRequest(capabilityRequest); 325 } 326 327 /** 328 * Perform service negotiation with the server asynchronously. 329 * 330 * @param completion Async completion object. 331 */ 332 public void negotiateServiceAsync(AsyncCompletion completion) { 333 Primitive serviceRequest = buildServiceRequest(); 334 AsyncTransaction tx = new AsyncTransaction( 335 mConnection.getTransactionManager(), completion) { 336 337 @Override 338 public void onResponseOk(Primitive response) { 339 mServiceTree = response.getElement(ImpsTags.AllFunctions).getFirstChild(); 340 } 341 342 @Override 343 public void onResponseError(ImpsErrorInfo error) { } 344 }; 345 346 tx.sendRequest(serviceRequest); 347 } 348 349 private Primitive buildCapabilityRequest() { 350 Primitive capabilityRequest = new Primitive(ImpsTags.ClientCapability_Request); 351 PrimitiveElement list = capabilityRequest.addElement(ImpsTags.CapabilityList); 352 list.addChild(ImpsTags.ClientType, ImpsClientCapability.getClientType()); 353 list.addChild(ImpsTags.AcceptedContentLength, Integer 354 .toString(ImpsClientCapability.getAcceptedContentLength())); 355 list.addChild(ImpsTags.ParserSize, 356 Integer.toString(ImpsClientCapability.getParserSize())); 357 list.addChild(ImpsTags.MultiTrans, 358 Integer.toString(ImpsClientCapability.getMultiTrans())); 359 360 // TODO: MultiTransPerMessage is IMPS 1.3 361 //list.addChild(ImpsTags.MultiTransPerMessage, 362 // Integer.toString(ImpsClientCapability.getMultiTransPerMessage())); 363 list.addChild(ImpsTags.InitialDeliveryMethod, 364 ImpsClientCapability.getInitialDeliveryMethod()); 365 list.addChild(ImpsTags.ServerPollMin, Long.toString(mServerPollMin)); 366 367 for(TransportType supportedBear : ImpsClientCapability.getSupportedBearers()) { 368 list.addChild(ImpsTags.SupportedBearer, supportedBear.toString()); 369 } 370 371 for(CirMethod supportedCirMethod : ImpsClientCapability.getSupportedCirMethods()) { 372 list.addChild(ImpsTags.SupportedCIRMethod, supportedCirMethod.toString()); 373 if (CirMethod.SUDP.equals(supportedCirMethod)) { 374 list.addChild(ImpsTags.UDPPort, 375 Integer.toString(mConnection.getConfig().getUdpPort())); 376 } 377 } 378 379 return capabilityRequest; 380 } 381 382 /* keep this method package private instead of private to avoid the 383 * overhead of calling from a inner class. 384 */ 385 void extractCapability(Primitive capabilityResponse) { 386 mSupportedCirMethod = new ArrayList<CirMethod>(); 387 388 PrimitiveElement agreedList = capabilityResponse.getContentElement() 389 .getFirstChild(); 390 for (PrimitiveElement element : agreedList.getChildren()) { 391 String tag = element.getTagName(); 392 if (tag.equals(ImpsTags.SupportedCIRMethod)) { 393 try { 394 mSupportedCirMethod.add(CirMethod.valueOf(element.getContents())); 395 } catch (IllegalArgumentException e) { 396 ImpsLog.log("Unrecognized CIR method " + element.getContents()); 397 } 398 } else if (tag.equals(ImpsTags.TCPAddress)) { 399 mCirTcpAddress = element.getContents(); 400 } else if (tag.equals(ImpsTags.TCPPort)) { 401 mCirTcpPort = (int)ImpsUtils.parseLong(element.getContents(), 402 DEFAULT_TCP_PORT); 403 } else if (tag.equals(ImpsTags.ServerPollMin)) { 404 long defaultPollMin = mConnection.getConfig().getDefaultServerPollMin(); 405 mServerPollMin = ImpsUtils.parseLong(element.getContents(), 406 defaultPollMin); 407 if (mServerPollMin <= 0) { 408 mServerPollMin = defaultPollMin; 409 } 410 } else if (tag.equals(ImpsTags.CIRHTTPAddress) 411 || tag.equals(ImpsTags.CIRURL)) { 412 // This tag is CIRHTTPAddress in 1.3 and CIRURL in 1.2 413 mCirHttpAddress = element.getChildContents(ImpsTags.URL); 414 } 415 } 416 } 417 418 private Primitive buildServiceRequest() { 419 Primitive serviceRequest = new Primitive(ImpsTags.Service_Request); 420 PrimitiveElement functions = serviceRequest.addElement(ImpsTags.Functions); 421 PrimitiveElement features = functions.addChild(ImpsTags.WVCSPFeat); 422 features.addChild(ImpsTags.FundamentalFeat); 423 features.addChild(ImpsTags.PresenceFeat); 424 features.addChild(ImpsTags.IMFeat); 425 features.addChild(ImpsTags.GroupFeat); 426 serviceRequest.addElement(ImpsTags.AllFunctionsRequest, true); 427 return serviceRequest; 428 } 429 } 430