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