1 /* 2 * Copyright (C) 2008 Esmertec AG. 3 * Copyright (C) 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 package com.android.im.imps; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.IOException; 22 import java.io.UnsupportedEncodingException; 23 import java.util.Iterator; 24 import java.util.LinkedList; 25 import java.util.concurrent.LinkedBlockingQueue; 26 27 import android.os.SystemClock; 28 29 import com.android.im.engine.HeartbeatService; 30 import com.android.im.engine.ImErrorInfo; 31 import com.android.im.engine.ImException; 32 import com.android.im.engine.SmsService; 33 import com.android.im.engine.SystemService; 34 import com.android.im.engine.SmsService.SmsListener; 35 import com.android.im.engine.SmsService.SmsSendFailureCallback; 36 37 public class SmsDataChannel extends DataChannel 38 implements SmsListener, HeartbeatService.Callback { 39 private SmsService mSmsService; 40 private String mSmsAddr; 41 private short mSmsPort; 42 43 private long mLastActive; 44 45 private SmsSplitter mSplitter; 46 private SmsAssembler mAssembler; 47 48 private LinkedBlockingQueue<Primitive> mReceiveQueue; 49 50 private ImpsTransactionManager mTxManager; 51 private boolean mConnected; 52 private long mKeepAliveMillis; 53 private Primitive mKeepAlivePrimitive; 54 55 private long mReplyTimeout; 56 private LinkedList<PendingTransaction> mPendingTransactions; 57 private Timer mTimer; 58 SmsDataChannel(ImpsConnection connection)59 protected SmsDataChannel(ImpsConnection connection) throws ImException { 60 super(connection); 61 62 mTxManager = connection.getTransactionManager(); 63 64 ImpsConnectionConfig config = connection.getConfig(); 65 mReplyTimeout = config.getReplyTimeout(); 66 mSmsAddr = config.getSmsAddr(); 67 mSmsPort = (short) config.getSmsPort(); 68 mSmsService = SystemService.getDefault().getSmsService(); 69 70 mParser = new PtsPrimitiveParser(); 71 try { 72 mSerializer = new PtsPrimitiveSerializer(config.getImpsVersion()); 73 } catch (SerializerException e) { 74 throw new ImException(e); 75 } 76 mSplitter = new SmsSplitter(mSmsService.getMaxSmsLength()); 77 mAssembler = new SmsAssembler(); 78 mAssembler.setSmsListener(this); 79 } 80 81 @Override connect()82 public void connect() throws ImException { 83 mSmsService.addSmsListener(mSmsAddr, mSmsPort, mAssembler); 84 mReceiveQueue = new LinkedBlockingQueue<Primitive>(); 85 mPendingTransactions = new LinkedList<PendingTransaction>(); 86 mTimer = new Timer(mReplyTimeout); 87 new Thread(mTimer, "SmsDataChannel timer").start(); 88 mConnected = true; 89 } 90 91 @Override getLastActiveTime()92 public long getLastActiveTime() { 93 return mLastActive; 94 } 95 96 @Override isSendingQueueEmpty()97 public boolean isSendingQueueEmpty() { 98 // Always true since we don't have a sending queue. 99 return true; 100 } 101 102 @Override receivePrimitive()103 public Primitive receivePrimitive() throws InterruptedException { 104 return mReceiveQueue.take(); 105 } 106 107 @Override sendPrimitive(Primitive p)108 public void sendPrimitive(Primitive p) { 109 ByteArrayOutputStream out = new ByteArrayOutputStream(); 110 try { 111 mSerializer.serialize(p, out); 112 mSplitter.split(out.toByteArray()); 113 SmsService smsService = SystemService.getDefault().getSmsService(); 114 SendFailureCallback sendFailureCallback 115 = new SendFailureCallback(p.getTransactionID()); 116 while (mSplitter.hasNext()) { 117 smsService.sendSms(mSmsAddr, mSmsPort, mSplitter.getNext(), 118 sendFailureCallback); 119 } 120 mLastActive = SystemClock.elapsedRealtime(); 121 addPendingTransaction(p.getTransactionID()); 122 } catch (IOException e) { 123 mTxManager.notifyErrorResponse(p.getTransactionID(), 124 ImpsErrorInfo.SERIALIZER_ERROR, e.getLocalizedMessage()); 125 } catch (SerializerException e) { 126 mTxManager.notifyErrorResponse(p.getTransactionID(), 127 ImpsErrorInfo.SERIALIZER_ERROR, e.getLocalizedMessage()); 128 } 129 } 130 131 @Override shutdown()132 public void shutdown() { 133 mSmsService.removeSmsListener(this); 134 mTimer.stop(); 135 mConnected = false; 136 } 137 138 @Override startKeepAlive(long interval)139 public void startKeepAlive(long interval) { 140 if (!mConnected) { 141 throw new IllegalStateException(); 142 } 143 144 if (interval <= 0) { 145 interval = mConnection.getConfig().getDefaultKeepAliveInterval(); 146 } 147 148 mKeepAliveMillis = interval * 1000; 149 if (mKeepAliveMillis < 0) { 150 ImpsLog.log("Negative keep alive time. Won't send keep-alive"); 151 } 152 mKeepAlivePrimitive = new Primitive(ImpsTags.KeepAlive_Request); 153 154 HeartbeatService heartbeatService 155 = SystemService.getDefault().getHeartbeatService(); 156 if (heartbeatService != null) { 157 heartbeatService.startHeartbeat(this, mKeepAliveMillis); 158 } 159 } 160 sendHeartbeat()161 public long sendHeartbeat() { 162 if (!mConnected) { 163 return 0; 164 } 165 166 long inactiveTime = SystemClock.elapsedRealtime() - mLastActive; 167 if (needSendKeepAlive(inactiveTime)) { 168 sendKeepAlive(); 169 return mKeepAliveMillis; 170 } else { 171 return mKeepAliveMillis - inactiveTime; 172 } 173 } 174 sendKeepAlive()175 private void sendKeepAlive() { 176 ImpsTransactionManager tm = mConnection.getTransactionManager(); 177 AsyncTransaction tx = new AsyncTransaction(tm) { 178 @Override 179 public void onResponseError(ImpsErrorInfo error) { 180 } 181 182 @Override 183 public void onResponseOk(Primitive response) { 184 // Since we never request a new timeout value, the response 185 // can be ignored 186 } 187 }; 188 tx.sendRequest(mKeepAlivePrimitive); 189 } 190 needSendKeepAlive(long inactiveTime)191 private boolean needSendKeepAlive(long inactiveTime) { 192 return mKeepAliveMillis - inactiveTime <= 500; 193 } 194 195 @Override resume()196 public boolean resume() { 197 return true; 198 } 199 200 @Override suspend()201 public void suspend() { 202 // do nothing. 203 } 204 onIncomingSms(byte[] data)205 public void onIncomingSms(byte[] data) { 206 try { 207 Primitive p = mParser.parse(new ByteArrayInputStream(data)); 208 mReceiveQueue.put(p); 209 removePendingTransaction(p.getTransactionID()); 210 } catch (ParserException e) { 211 handleError(data, ImpsErrorInfo.PARSER_ERROR, e.getLocalizedMessage()); 212 } catch (IOException e) { 213 handleError(data, ImpsErrorInfo.PARSER_ERROR, e.getLocalizedMessage()); 214 } catch (InterruptedException e) { 215 handleError(data, ImpsErrorInfo.UNKNOWN_ERROR, e.getLocalizedMessage()); 216 } 217 } 218 handleError(byte[] data, int errCode, String info)219 private void handleError(byte[] data, int errCode, String info) { 220 String trId = extractTrId(data); 221 if (trId != null) { 222 mTxManager.notifyErrorResponse(trId, errCode, info); 223 removePendingTransaction(trId); 224 } 225 } 226 extractTrId(byte[] data)227 private String extractTrId(byte[] data) { 228 int transIdStart = 4; 229 int index = transIdStart; 230 while(Character.isDigit(data[index])) { 231 index++; 232 } 233 int transIdLen = index - transIdStart; 234 try { 235 return new String(data, transIdStart, transIdLen, "UTF-8"); 236 } catch (UnsupportedEncodingException e) { 237 return null; 238 } 239 } 240 addPendingTransaction(String transId)241 private void addPendingTransaction(String transId) { 242 synchronized (mPendingTransactions) { 243 mPendingTransactions.add(new PendingTransaction(transId)); 244 } 245 } 246 removePendingTransaction(String transId)247 private void removePendingTransaction(String transId) { 248 synchronized (mPendingTransactions) { 249 Iterator<PendingTransaction> iter = mPendingTransactions.iterator(); 250 while (iter.hasNext()) { 251 PendingTransaction tx = iter.next(); 252 if (tx.mTransId.equals(transId)) { 253 iter.remove(); 254 break; 255 } 256 } 257 } 258 } 259 checkTimeout()260 /*package*/void checkTimeout() { 261 synchronized (mPendingTransactions) { 262 Iterator<PendingTransaction> iter = mPendingTransactions.iterator(); 263 while (iter.hasNext()) { 264 PendingTransaction tx = iter.next(); 265 if (tx.isExpired(mReplyTimeout)) { 266 notifyTimeout(tx); 267 } else { 268 break; 269 } 270 } 271 } 272 } 273 notifyTimeout(PendingTransaction tx)274 private void notifyTimeout(PendingTransaction tx) { 275 String transId = tx.mTransId; 276 mTxManager.notifyErrorResponse(transId, ImpsErrorInfo.TIMEOUT, 277 "Timeout"); 278 removePendingTransaction(transId); 279 } 280 281 private class SendFailureCallback implements SmsSendFailureCallback { 282 private String mTransId; 283 SendFailureCallback(String transId)284 public SendFailureCallback(String transId) { 285 mTransId = transId; 286 } 287 onFailure(int errorCode)288 public void onFailure(int errorCode) { 289 mTxManager.notifyErrorResponse(mTransId, ImErrorInfo.NETWORK_ERROR, null); 290 } 291 } 292 293 private class Timer implements Runnable { 294 private boolean mStopped; 295 private long mInterval; 296 Timer(long interval)297 public Timer(long interval) { 298 mInterval = interval; 299 mStopped = false; 300 } 301 stop()302 public void stop() { 303 mStopped = true; 304 } 305 run()306 public void run() { 307 while (!mStopped) { 308 try { 309 Thread.sleep(mInterval); 310 } catch (InterruptedException e) { 311 continue; 312 } 313 checkTimeout(); 314 } 315 } 316 } 317 318 private static class PendingTransaction { 319 private String mTransId; 320 private long mSentTime; 321 PendingTransaction(String transId)322 public PendingTransaction(String transId) { 323 mTransId = transId; 324 } 325 isExpired(long timeout)326 public boolean isExpired(long timeout) { 327 return SystemClock.elapsedRealtime() - mSentTime >= timeout; 328 } 329 } 330 } 331