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.io.BufferedReader; 21 import java.io.IOException; 22 import java.io.InputStreamReader; 23 import java.net.Socket; 24 import java.net.UnknownHostException; 25 import com.android.im.engine.HeartbeatService; 26 import com.android.im.engine.ImErrorInfo; 27 import com.android.im.engine.ImException; 28 import com.android.im.engine.SystemService; 29 30 import android.os.SystemClock; 31 import android.util.Log; 32 33 /** 34 * An implementation of CIR channel with standalone TCP/IP banding. 35 */ 36 class TcpCirChannel extends CirChannel implements Runnable, HeartbeatService.Callback { 37 public static final int PING_INTERVAL = 20 * 60 * 1000; // 20 min 38 39 private static final int OK_TIMEOUT = 30000; 40 41 private String mAddress; 42 private int mPort; 43 private boolean mDone; 44 private boolean mReconnecting; 45 private Object mReconnectLock = new Object(); 46 private Socket mSocket; 47 48 private boolean mWaitForOK; 49 private long mLastActive; 50 private String mUser; 51 private BufferedReader mReader; 52 53 private Thread mCirThread; 54 TcpCirChannel(ImpsConnection connection)55 protected TcpCirChannel(ImpsConnection connection) { 56 super(connection); 57 mAddress = connection.getSession().getCirTcpAddress(); 58 mPort = connection.getSession().getCirTcpPort(); 59 mUser = connection.getSession().getLoginUser().getName(); 60 } 61 62 @Override connect()63 public synchronized void connect() throws ImException { 64 try { 65 mDone = false; 66 connectServer(); 67 mCirThread = new Thread(this, "TcpCirChannel"); 68 mCirThread.setDaemon(true); 69 mCirThread.start(); 70 HeartbeatService heartbeatService 71 = SystemService.getDefault().getHeartbeatService(); 72 if (heartbeatService != null) { 73 heartbeatService.startHeartbeat(this, PING_INTERVAL); 74 } 75 } catch (UnknownHostException e) { 76 throw new ImException(ImErrorInfo.UNKNOWN_SERVER, 77 "Can't find the TCP CIR server"); 78 } catch (IOException e) { 79 throw new ImException(ImErrorInfo.CANT_CONNECT_TO_SERVER, 80 "Can't connect to the TCP CIR server"); 81 } 82 } 83 84 @Override shutdown()85 public synchronized void shutdown() { 86 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 87 ImpsLog.log(mUser + " Shutting down CIR channel"); 88 } 89 mDone = true; 90 if (mReconnecting) { 91 synchronized (mReconnectLock) { 92 mReconnecting = false; 93 mReconnectLock.notify(); 94 } 95 } 96 try { 97 if(mSocket != null) { 98 mSocket.close(); 99 } 100 } catch (IOException e) { 101 // ignore 102 } 103 HeartbeatService heartbeatService 104 = SystemService.getDefault().getHeartbeatService(); 105 if (heartbeatService != null) { 106 heartbeatService.stopHeartbeat(this); 107 } 108 } 109 isShutdown()110 public boolean isShutdown() { 111 return mDone; 112 } 113 run()114 public void run() { 115 while (!mDone) { 116 try { 117 if (mWaitForOK && SystemClock.elapsedRealtime() - mLastActive 118 > OK_TIMEOUT) { 119 // OMA-TS-IMPS_CSP_Transport-V1_3-20070123-A 8.1.3: 120 // If client doesn't receive an "OK" message or detects 121 // that the connection is broken, it MUST open a new 122 // TCP/IP connection and send the "HELO" message again. 123 reconnectAndWait(); 124 } 125 126 String line = mReader.readLine(); 127 mLastActive = SystemClock.elapsedRealtime(); 128 129 if (line == null) { 130 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 131 ImpsLog.log(mUser + " TCP CIR: socket closed by server."); 132 } 133 reconnectAndWait(); 134 } else if ("OK".equals(line)) { 135 mWaitForOK = false; 136 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 137 ImpsLog.log(mUser + " << TCP CIR: OK Received"); 138 } 139 // TODO: Since we just have one thread per TCP CIR 140 // connection now, the session cookie is ignored. 141 } else if (line.startsWith("WVCI")) { 142 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 143 ImpsLog.log(mUser + " << TCP CIR: CIR Received"); 144 } 145 if (!mDone) { 146 mConnection.sendPollingRequest(); 147 } 148 } 149 } catch (IOException e) { 150 ImpsLog.logError("TCP CIR channel get:" + e); 151 if(!mDone){ 152 reconnectAndWait(); 153 } 154 } 155 } 156 if (mReader != null) { 157 try { 158 mReader.close(); 159 } catch (IOException e) { 160 e.printStackTrace(); 161 } 162 } 163 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 164 ImpsLog.log(mUser + " CIR channel thread quit"); 165 } 166 } 167 168 @Override reconnect()169 public void reconnect() { 170 synchronized (mReconnectLock) { 171 if (mReconnecting) { 172 return; 173 } else { 174 mReconnecting = true; 175 } 176 } 177 178 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 179 ImpsLog.log(mUser + " CIR channel reconnecting"); 180 } 181 long waitTime = 3000; 182 while (!mDone) { // Keep trying to connect the server until shutdown 183 try { 184 try { 185 Thread.sleep(waitTime); 186 } catch (InterruptedException e) { 187 } 188 connectServer(); 189 // Send a polling request to make sure we don't miss anything 190 // while CIR is down. 191 if(!mDone) { 192 mConnection.sendPollingRequest(); 193 } 194 break; 195 } catch (IOException e) { 196 waitTime *= 3; 197 if(waitTime > 27000) { 198 waitTime = 3000; 199 if(!mDone){ 200 mConnection.sendPollingRequest(); 201 } 202 } 203 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 204 ImpsLog.log(mUser + " CIR channel reconnect fail, retry after " 205 + waitTime / 1000 + " seconds"); 206 } 207 } 208 } 209 synchronized (mReconnectLock) { 210 mReconnecting = false; 211 mReconnectLock.notify(); 212 } 213 } 214 reconnectAndWait()215 private void reconnectAndWait() { 216 reconnect(); 217 // in case reconnect() has already been called in another thread, wait 218 // for it to finish 219 while (!mDone) { 220 synchronized (mReconnectLock) { 221 if (mReconnecting) { 222 try { 223 mReconnectLock.wait(); 224 } catch (InterruptedException e) { 225 // ignore 226 } 227 } else { 228 break; 229 } 230 } 231 } 232 } 233 connectServer()234 private synchronized void connectServer() throws IOException { 235 if(!mDone) { 236 if (mSocket != null) { 237 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 238 ImpsLog.log(mUser + " TCP CIR: close previous socket"); 239 } 240 try { 241 mSocket.close(); 242 } catch (IOException e) { 243 // ignore 244 } 245 } 246 247 mSocket = new Socket(mAddress, mPort); 248 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 249 ImpsLog.log(mUser + " >> TCP CIR: HELO"); 250 } 251 sendData("HELO " + mConnection.getSession().getID() + "\r\n"); 252 if (mReader != null) { 253 try { 254 mReader.close(); 255 } catch (IOException e) { 256 // ignore 257 } 258 } 259 mReader = new BufferedReader( 260 new InputStreamReader(mSocket.getInputStream(), "UTF-8"), 261 8192); 262 } 263 } 264 sendHeartbeat()265 public synchronized long sendHeartbeat() { 266 if (mDone) { 267 return 0; 268 } 269 270 long inactiveTime = SystemClock.elapsedRealtime() - mLastActive; 271 if(needSendPing(inactiveTime)) { 272 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 273 ImpsLog.log(mUser + " >> TCP CIR: PING"); 274 } 275 try { 276 sendData("PING \r\n"); 277 } catch (IOException e) { 278 if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { 279 ImpsLog.log("Failed to send PING, try to reconnect"); 280 } 281 reconnect(); 282 } 283 return PING_INTERVAL; 284 } else { 285 return PING_INTERVAL - inactiveTime; 286 } 287 } 288 needSendPing(long inactiveTime)289 private boolean needSendPing(long inactiveTime) { 290 return (PING_INTERVAL - inactiveTime) < 500; 291 } 292 sendData(String s)293 private void sendData(String s) throws IOException { 294 mSocket.getOutputStream().write(s.getBytes("UTF-8")); 295 mWaitForOK = true; 296 mLastActive = SystemClock.elapsedRealtime(); 297 } 298 299 } 300