blob: a5f8d6b1770ce2c0cd319dd54e9f0a2af565a1d2 [file] [log] [blame]
/*
* Copyright (C) 2008 Esmertec AG.
* Copyright (C) 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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingQueue;
import android.os.SystemClock;
import com.android.im.engine.HeartbeatService;
import com.android.im.engine.ImErrorInfo;
import com.android.im.engine.ImException;
import com.android.im.engine.SmsService;
import com.android.im.engine.SystemService;
import com.android.im.engine.SmsService.SmsListener;
import com.android.im.engine.SmsService.SmsSendFailureCallback;
public class SmsDataChannel extends DataChannel
implements SmsListener, HeartbeatService.Callback {
private SmsService mSmsService;
private String mSmsAddr;
private short mSmsPort;
private long mLastActive;
private SmsSplitter mSplitter;
private SmsAssembler mAssembler;
private LinkedBlockingQueue<Primitive> mReceiveQueue;
private ImpsTransactionManager mTxManager;
private boolean mConnected;
private long mKeepAliveMillis;
private Primitive mKeepAlivePrimitive;
private long mReplyTimeout;
private LinkedList<PendingTransaction> mPendingTransactions;
private Timer mTimer;
protected SmsDataChannel(ImpsConnection connection) throws ImException {
super(connection);
mTxManager = connection.getTransactionManager();
ImpsConnectionConfig config = connection.getConfig();
mReplyTimeout = config.getReplyTimeout();
mSmsAddr = config.getSmsAddr();
mSmsPort = (short) config.getSmsPort();
mSmsService = SystemService.getDefault().getSmsService();
mParser = new PtsPrimitiveParser();
try {
mSerializer = new PtsPrimitiveSerializer(config.getImpsVersion());
} catch (SerializerException e) {
throw new ImException(e);
}
mSplitter = new SmsSplitter(mSmsService.getMaxSmsLength());
mAssembler = new SmsAssembler();
mAssembler.setSmsListener(this);
}
@Override
public void connect() throws ImException {
mSmsService.addSmsListener(mSmsAddr, mSmsPort, mAssembler);
mReceiveQueue = new LinkedBlockingQueue<Primitive>();
mPendingTransactions = new LinkedList<PendingTransaction>();
mTimer = new Timer(mReplyTimeout);
new Thread(mTimer, "SmsDataChannel timer").start();
mConnected = true;
}
@Override
public long getLastActiveTime() {
return mLastActive;
}
@Override
public boolean isSendingQueueEmpty() {
// Always true since we don't have a sending queue.
return true;
}
@Override
public Primitive receivePrimitive() throws InterruptedException {
return mReceiveQueue.take();
}
@Override
public void sendPrimitive(Primitive p) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
mSerializer.serialize(p, out);
mSplitter.split(out.toByteArray());
SmsService smsService = SystemService.getDefault().getSmsService();
SendFailureCallback sendFailureCallback
= new SendFailureCallback(p.getTransactionID());
while (mSplitter.hasNext()) {
smsService.sendSms(mSmsAddr, mSmsPort, mSplitter.getNext(),
sendFailureCallback);
}
mLastActive = SystemClock.elapsedRealtime();
addPendingTransaction(p.getTransactionID());
} catch (IOException e) {
mTxManager.notifyErrorResponse(p.getTransactionID(),
ImpsErrorInfo.SERIALIZER_ERROR, e.getLocalizedMessage());
} catch (SerializerException e) {
mTxManager.notifyErrorResponse(p.getTransactionID(),
ImpsErrorInfo.SERIALIZER_ERROR, e.getLocalizedMessage());
}
}
@Override
public void shutdown() {
mSmsService.removeSmsListener(this);
mTimer.stop();
mConnected = false;
}
@Override
public void startKeepAlive(long interval) {
if (!mConnected) {
throw new IllegalStateException();
}
if (interval <= 0) {
interval = mConnection.getConfig().getDefaultKeepAliveInterval();
}
mKeepAliveMillis = interval * 1000;
if (mKeepAliveMillis < 0) {
ImpsLog.log("Negative keep alive time. Won't send keep-alive");
}
mKeepAlivePrimitive = new Primitive(ImpsTags.KeepAlive_Request);
HeartbeatService heartbeatService
= SystemService.getDefault().getHeartbeatService();
if (heartbeatService != null) {
heartbeatService.startHeartbeat(this, mKeepAliveMillis);
}
}
public long sendHeartbeat() {
if (!mConnected) {
return 0;
}
long inactiveTime = SystemClock.elapsedRealtime() - mLastActive;
if (needSendKeepAlive(inactiveTime)) {
sendKeepAlive();
return mKeepAliveMillis;
} else {
return mKeepAliveMillis - inactiveTime;
}
}
private void sendKeepAlive() {
ImpsTransactionManager tm = mConnection.getTransactionManager();
AsyncTransaction tx = new AsyncTransaction(tm) {
@Override
public void onResponseError(ImpsErrorInfo error) {
}
@Override
public void onResponseOk(Primitive response) {
// Since we never request a new timeout value, the response
// can be ignored
}
};
tx.sendRequest(mKeepAlivePrimitive);
}
private boolean needSendKeepAlive(long inactiveTime) {
return mKeepAliveMillis - inactiveTime <= 500;
}
@Override
public boolean resume() {
return true;
}
@Override
public void suspend() {
// do nothing.
}
public void onIncomingSms(byte[] data) {
try {
Primitive p = mParser.parse(new ByteArrayInputStream(data));
mReceiveQueue.put(p);
removePendingTransaction(p.getTransactionID());
} catch (ParserException e) {
handleError(data, ImpsErrorInfo.PARSER_ERROR, e.getLocalizedMessage());
} catch (IOException e) {
handleError(data, ImpsErrorInfo.PARSER_ERROR, e.getLocalizedMessage());
} catch (InterruptedException e) {
handleError(data, ImpsErrorInfo.UNKNOWN_ERROR, e.getLocalizedMessage());
}
}
private void handleError(byte[] data, int errCode, String info) {
String trId = extractTrId(data);
if (trId != null) {
mTxManager.notifyErrorResponse(trId, errCode, info);
removePendingTransaction(trId);
}
}
private String extractTrId(byte[] data) {
int transIdStart = 4;
int index = transIdStart;
while(Character.isDigit(data[index])) {
index++;
}
int transIdLen = index - transIdStart;
try {
return new String(data, transIdStart, transIdLen, "UTF-8");
} catch (UnsupportedEncodingException e) {
return null;
}
}
private void addPendingTransaction(String transId) {
synchronized (mPendingTransactions) {
mPendingTransactions.add(new PendingTransaction(transId));
}
}
private void removePendingTransaction(String transId) {
synchronized (mPendingTransactions) {
Iterator<PendingTransaction> iter = mPendingTransactions.iterator();
while (iter.hasNext()) {
PendingTransaction tx = iter.next();
if (tx.mTransId.equals(transId)) {
iter.remove();
break;
}
}
}
}
/*package*/void checkTimeout() {
synchronized (mPendingTransactions) {
Iterator<PendingTransaction> iter = mPendingTransactions.iterator();
while (iter.hasNext()) {
PendingTransaction tx = iter.next();
if (tx.isExpired(mReplyTimeout)) {
notifyTimeout(tx);
} else {
break;
}
}
}
}
private void notifyTimeout(PendingTransaction tx) {
String transId = tx.mTransId;
mTxManager.notifyErrorResponse(transId, ImpsErrorInfo.TIMEOUT,
"Timeout");
removePendingTransaction(transId);
}
private class SendFailureCallback implements SmsSendFailureCallback {
private String mTransId;
public SendFailureCallback(String transId) {
mTransId = transId;
}
public void onFailure(int errorCode) {
mTxManager.notifyErrorResponse(mTransId, ImErrorInfo.NETWORK_ERROR, null);
}
}
private class Timer implements Runnable {
private boolean mStopped;
private long mInterval;
public Timer(long interval) {
mInterval = interval;
mStopped = false;
}
public void stop() {
mStopped = true;
}
public void run() {
while (!mStopped) {
try {
Thread.sleep(mInterval);
} catch (InterruptedException e) {
continue;
}
checkTimeout();
}
}
}
private static class PendingTransaction {
private String mTransId;
private long mSentTime;
public PendingTransaction(String transId) {
mTransId = transId;
}
public boolean isExpired(long timeout) {
return SystemClock.elapsedRealtime() - mSentTime >= timeout;
}
}
}