• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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