| /* |
| * Copyright (C) 2007-2008 Esmertec AG. |
| * Copyright (C) 2007-2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.im.imps; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.net.Socket; |
| import java.net.UnknownHostException; |
| import com.android.im.engine.HeartbeatService; |
| import com.android.im.engine.ImErrorInfo; |
| import com.android.im.engine.ImException; |
| import com.android.im.engine.SystemService; |
| |
| import android.os.SystemClock; |
| import android.util.Log; |
| |
| /** |
| * An implementation of CIR channel with standalone TCP/IP banding. |
| */ |
| class TcpCirChannel extends CirChannel implements Runnable, HeartbeatService.Callback { |
| public static final int PING_INTERVAL = 20 * 60 * 1000; // 20 min |
| |
| private static final int OK_TIMEOUT = 30000; |
| |
| private String mAddress; |
| private int mPort; |
| private boolean mDone; |
| private boolean mReconnecting; |
| private Object mReconnectLock = new Object(); |
| private Socket mSocket; |
| |
| private boolean mWaitForOK; |
| private long mLastActive; |
| private String mUser; |
| private BufferedReader mReader; |
| |
| private Thread mCirThread; |
| |
| protected TcpCirChannel(ImpsConnection connection) { |
| super(connection); |
| mAddress = connection.getSession().getCirTcpAddress(); |
| mPort = connection.getSession().getCirTcpPort(); |
| mUser = connection.getSession().getLoginUser().getName(); |
| } |
| |
| @Override |
| public synchronized void connect() throws ImException { |
| try { |
| mDone = false; |
| connectServer(); |
| mCirThread = new Thread(this, "TcpCirChannel"); |
| mCirThread.setDaemon(true); |
| mCirThread.start(); |
| HeartbeatService heartbeatService |
| = SystemService.getDefault().getHeartbeatService(); |
| if (heartbeatService != null) { |
| heartbeatService.startHeartbeat(this, PING_INTERVAL); |
| } |
| } catch (UnknownHostException e) { |
| throw new ImException(ImErrorInfo.UNKNOWN_SERVER, |
| "Can't find the TCP CIR server"); |
| } catch (IOException e) { |
| throw new ImException(ImErrorInfo.CANT_CONNECT_TO_SERVER, |
| "Can't connect to the TCP CIR server"); |
| } |
| } |
| |
| @Override |
| public synchronized void shutdown() { |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " Shutting down CIR channel"); |
| } |
| mDone = true; |
| if (mReconnecting) { |
| synchronized (mReconnectLock) { |
| mReconnecting = false; |
| mReconnectLock.notify(); |
| } |
| } |
| try { |
| if(mSocket != null) { |
| mSocket.close(); |
| } |
| } catch (IOException e) { |
| // ignore |
| } |
| HeartbeatService heartbeatService |
| = SystemService.getDefault().getHeartbeatService(); |
| if (heartbeatService != null) { |
| heartbeatService.stopHeartbeat(this); |
| } |
| } |
| |
| public boolean isShutdown() { |
| return mDone; |
| } |
| |
| public void run() { |
| while (!mDone) { |
| try { |
| if (mWaitForOK && SystemClock.elapsedRealtime() - mLastActive |
| > OK_TIMEOUT) { |
| // OMA-TS-IMPS_CSP_Transport-V1_3-20070123-A 8.1.3: |
| // If client doesn't receive an "OK" message or detects |
| // that the connection is broken, it MUST open a new |
| // TCP/IP connection and send the "HELO" message again. |
| reconnectAndWait(); |
| } |
| |
| String line = mReader.readLine(); |
| mLastActive = SystemClock.elapsedRealtime(); |
| |
| if (line == null) { |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " TCP CIR: socket closed by server."); |
| } |
| reconnectAndWait(); |
| } else if ("OK".equals(line)) { |
| mWaitForOK = false; |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " << TCP CIR: OK Received"); |
| } |
| // TODO: Since we just have one thread per TCP CIR |
| // connection now, the session cookie is ignored. |
| } else if (line.startsWith("WVCI")) { |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " << TCP CIR: CIR Received"); |
| } |
| if (!mDone) { |
| mConnection.sendPollingRequest(); |
| } |
| } |
| } catch (IOException e) { |
| ImpsLog.logError("TCP CIR channel get:" + e); |
| if(!mDone){ |
| reconnectAndWait(); |
| } |
| } |
| } |
| if (mReader != null) { |
| try { |
| mReader.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " CIR channel thread quit"); |
| } |
| } |
| |
| @Override |
| public void reconnect() { |
| synchronized (mReconnectLock) { |
| if (mReconnecting) { |
| return; |
| } else { |
| mReconnecting = true; |
| } |
| } |
| |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " CIR channel reconnecting"); |
| } |
| long waitTime = 3000; |
| while (!mDone) { // Keep trying to connect the server until shutdown |
| try { |
| try { |
| Thread.sleep(waitTime); |
| } catch (InterruptedException e) { |
| } |
| connectServer(); |
| // Send a polling request to make sure we don't miss anything |
| // while CIR is down. |
| if(!mDone) { |
| mConnection.sendPollingRequest(); |
| } |
| break; |
| } catch (IOException e) { |
| waitTime *= 3; |
| if(waitTime > 27000) { |
| waitTime = 3000; |
| if(!mDone){ |
| mConnection.sendPollingRequest(); |
| } |
| } |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " CIR channel reconnect fail, retry after " |
| + waitTime / 1000 + " seconds"); |
| } |
| } |
| } |
| synchronized (mReconnectLock) { |
| mReconnecting = false; |
| mReconnectLock.notify(); |
| } |
| } |
| |
| private void reconnectAndWait() { |
| reconnect(); |
| // in case reconnect() has already been called in another thread, wait |
| // for it to finish |
| while (!mDone) { |
| synchronized (mReconnectLock) { |
| if (mReconnecting) { |
| try { |
| mReconnectLock.wait(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } else { |
| break; |
| } |
| } |
| } |
| } |
| |
| private synchronized void connectServer() throws IOException { |
| if(!mDone) { |
| if (mSocket != null) { |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " TCP CIR: close previous socket"); |
| } |
| try { |
| mSocket.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| |
| mSocket = new Socket(mAddress, mPort); |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " >> TCP CIR: HELO"); |
| } |
| sendData("HELO " + mConnection.getSession().getID() + "\r\n"); |
| if (mReader != null) { |
| try { |
| mReader.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| mReader = new BufferedReader( |
| new InputStreamReader(mSocket.getInputStream(), "UTF-8"), |
| 8192); |
| } |
| } |
| |
| public synchronized long sendHeartbeat() { |
| if (mDone) { |
| return 0; |
| } |
| |
| long inactiveTime = SystemClock.elapsedRealtime() - mLastActive; |
| if(needSendPing(inactiveTime)) { |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log(mUser + " >> TCP CIR: PING"); |
| } |
| try { |
| sendData("PING \r\n"); |
| } catch (IOException e) { |
| if (Log.isLoggable(ImpsLog.TAG, Log.DEBUG)) { |
| ImpsLog.log("Failed to send PING, try to reconnect"); |
| } |
| reconnect(); |
| } |
| return PING_INTERVAL; |
| } else { |
| return PING_INTERVAL - inactiveTime; |
| } |
| } |
| |
| private boolean needSendPing(long inactiveTime) { |
| return (PING_INTERVAL - inactiveTime) < 500; |
| } |
| |
| private void sendData(String s) throws IOException { |
| mSocket.getOutputStream().write(s.getBytes("UTF-8")); |
| mWaitForOK = true; |
| mLastActive = SystemClock.elapsedRealtime(); |
| } |
| |
| } |