2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.internal.telephony.cdma;
20 import android.app.Activity;
21 import android.app.PendingIntent;
22 import android.app.PendingIntent.CanceledException;
23 import android.content.ContentValues;
24 import android.content.Intent;
25 import android.content.SharedPreferences;
26 import android.database.Cursor;
27 import android.database.SQLException;
28 import android.os.AsyncResult;
29 import android.os.Message;
30 import android.os.SystemProperties;
31 import android.preference.PreferenceManager;
32 import android.provider.Telephony;
33 import android.provider.Telephony.Sms.Intents;
34 import android.telephony.SmsManager;
35 import android.telephony.SmsMessage.MessageClass;
36 import android.util.Config;
37 import android.util.Log;
39 import com.android.internal.telephony.CommandsInterface;
40 import com.android.internal.telephony.SMSDispatcher;
41 import com.android.internal.telephony.SmsHeader;
42 import com.android.internal.telephony.SmsMessageBase;
43 import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
44 import com.android.internal.telephony.TelephonyProperties;
45 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
46 import com.android.internal.telephony.cdma.sms.UserData;
47 import com.android.internal.util.HexDump;
49 import java.io.ByteArrayOutputStream;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.HashMap;
55 final class CdmaSMSDispatcher extends SMSDispatcher {
56 private static final String TAG = "CDMA";
58 private byte[] mLastDispatchedSmsFingerprint;
59 private byte[] mLastAcknowledgedSmsFingerprint;
61 CdmaSMSDispatcher(CDMAPhone phone) {
66 * Called when a status report is received. This should correspond to
67 * a previously successful SEND.
68 * Is a special GSM function, should never be called in CDMA!!
70 * @param ar AsyncResult passed into the message handler. ar.result should
71 * be a String representing the status report PDU, as ASCII hex.
74 protected void handleStatusReport(AsyncResult ar) {
75 Log.d(TAG, "handleStatusReport is a special GSM function, should never be called in CDMA!");
78 private void handleCdmaStatusReport(SmsMessage sms) {
79 for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
80 SmsTracker tracker = deliveryPendingList.get(i);
81 if (tracker.mMessageRef == sms.messageRef) {
82 // Found it. Remove from list and broadcast.
83 deliveryPendingList.remove(i);
84 PendingIntent intent = tracker.mDeliveryIntent;
85 Intent fillIn = new Intent();
86 fillIn.putExtra("pdu", sms.getPdu());
88 intent.send(mContext, Activity.RESULT_OK, fillIn);
89 } catch (CanceledException ex) {}
90 break; // Only expect to see one tracker matching this message.
97 public int dispatchMessage(SmsMessageBase smsb) {
99 // If sms is null, means there was a parsing error.
101 Log.e(TAG, "dispatchMessage: message is null");
102 return Intents.RESULT_SMS_GENERIC_ERROR;
105 String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
106 if (inEcm.equals("true")) {
107 return Activity.RESULT_OK;
110 if (mSmsReceiveDisabled) {
111 // Device doesn't support receiving SMS,
112 Log.d(TAG, "Received short message on device which doesn't support "
113 + "receiving SMS. Ignored.");
114 return Intents.RESULT_SMS_HANDLED;
117 // See if we have a network duplicate SMS.
118 SmsMessage sms = (SmsMessage) smsb;
119 mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
120 if (mLastAcknowledgedSmsFingerprint != null &&
121 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
122 return Intents.RESULT_SMS_HANDLED;
124 // Decode BD stream and set sms variables.
126 int teleService = sms.getTeleService();
127 boolean handled = false;
129 if ((SmsEnvelope.TELESERVICE_VMN == teleService) ||
130 (SmsEnvelope.TELESERVICE_MWI == teleService)) {
131 // handling Voicemail
132 int voicemailCount = sms.getNumOfVoicemails();
133 Log.d(TAG, "Voicemail count=" + voicemailCount);
134 // Store the voicemail count in preferences.
135 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
136 mPhone.getContext());
137 SharedPreferences.Editor editor = sp.edit();
138 editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
140 ((CDMAPhone) mPhone).updateMessageWaitingIndicator(voicemailCount);
142 } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
143 (SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
144 sms.isStatusReportMessage()) {
145 handleCdmaStatusReport(sms);
147 } else if ((sms.getUserData() == null)) {
149 Log.d(TAG, "Received SMS without user data");
155 return Intents.RESULT_SMS_HANDLED;
158 if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) {
159 // It's a storable message and there's no storage available. Bail.
160 // (See C.S0015-B v2.0 for a description of "Immediate Display"
161 // messages, which we represent as CLASS_0.)
162 return Intents.RESULT_SMS_OUT_OF_MEMORY;
165 if (SmsEnvelope.TELESERVICE_WAP == teleService) {
166 return processCdmaWapPdu(sms.getUserData(), sms.messageRef,
167 sms.getOriginatingAddress());
170 // Reject (NAK) any messages with teleservice ids that have
171 // not yet been handled and also do not correspond to the two
172 // kinds that are processed below.
173 if ((SmsEnvelope.TELESERVICE_WMT != teleService) &&
174 (SmsEnvelope.TELESERVICE_WEMT != teleService) &&
175 (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) {
176 return Intents.RESULT_SMS_UNSUPPORTED;
180 * TODO(cleanup): Why are we using a getter method for this
181 * (and for so many other sms fields)? Trivial getters and
182 * setters like this are direct violations of the style guide.
183 * If the purpose is to protect against writes (by not
184 * providing a setter) then any protection is illusory (and
185 * hence bad) for cases where the values are not primitives,
186 * such as this call for the header. Since this is an issue
187 * with the public API it cannot be changed easily, but maybe
188 * something can be done eventually.
190 SmsHeader smsHeader = sms.getUserDataHeader();
193 * TODO(cleanup): Since both CDMA and GSM use the same header
194 * format, this dispatch processing is naturally identical,
195 * and code should probably not be replicated explicitly.
198 // See if message is partial or port addressed.
199 if ((smsHeader == null) || (smsHeader.concatRef == null)) {
200 // Message is not partial (not part of concatenated sequence).
201 byte[][] pdus = new byte[1][];
202 pdus[0] = sms.getPdu();
204 if (smsHeader != null && smsHeader.portAddrs != null) {
205 if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
206 // GSM-style WAP indication
207 return mWapPush.dispatchWapPdu(sms.getUserData());
209 // The message was sent to a port, so concoct a URI for it.
210 dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
213 // Normal short and non-port-addressed message, dispatch it.
216 return Activity.RESULT_OK;
218 // Process the message part.
219 return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
224 * Processes inbound messages that are in the WAP-WDP PDU format. See
225 * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
226 * WDP segments are gathered until a datagram completes and gets dispatched.
228 * @param pdu The WAP-WDP PDU segment
229 * @return a result code from {@link Telephony.Sms.Intents}, or
230 * {@link Activity#RESULT_OK} if the message has been broadcast
233 protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
240 int destinationPort = 0;
242 msgType = pdu[index++];
244 Log.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
245 return Intents.RESULT_SMS_HANDLED;
247 totalSegments = pdu[index++]; // >=1
248 segment = pdu[index++]; // >=0
250 // Only the first segment contains sourcePort and destination Port
252 //process WDP segment
253 sourcePort = (0xFF & pdu[index++]) << 8;
254 sourcePort |= 0xFF & pdu[index++];
255 destinationPort = (0xFF & pdu[index++]) << 8;
256 destinationPort |= 0xFF & pdu[index++];
259 // Lookup all other related parts
260 StringBuilder where = new StringBuilder("reference_number =");
261 where.append(referenceNumber);
262 where.append(" AND address = ?");
263 String[] whereArgs = new String[] {address};
265 Log.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
266 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
267 + ", ID = " + referenceNumber + ", segment# = " + segment + "/" + totalSegments);
269 byte[][] pdus = null;
270 Cursor cursor = null;
272 cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
273 int cursorCount = cursor.getCount();
274 if (cursorCount != totalSegments - 1) {
275 // We don't have all the parts yet, store this one away
276 ContentValues values = new ContentValues();
277 values.put("date", new Long(0));
278 values.put("pdu", HexDump.toHexString(pdu, index, pdu.length - index));
279 values.put("address", address);
280 values.put("reference_number", referenceNumber);
281 values.put("count", totalSegments);
282 values.put("sequence", segment);
283 values.put("destination_port", destinationPort);
285 mResolver.insert(mRawUri, values);
287 return Intents.RESULT_SMS_HANDLED;
290 // All the parts are in place, deal with them
291 int pduColumn = cursor.getColumnIndex("pdu");
292 int sequenceColumn = cursor.getColumnIndex("sequence");
294 pdus = new byte[totalSegments][];
295 for (int i = 0; i < cursorCount; i++) {
297 int cursorSequence = (int)cursor.getLong(sequenceColumn);
298 // Read the destination port from the first segment
299 if (cursorSequence == 0) {
300 int destinationPortColumn = cursor.getColumnIndex("destination_port");
301 destinationPort = (int)cursor.getLong(destinationPortColumn);
303 pdus[cursorSequence] = HexDump.hexStringToByteArray(
304 cursor.getString(pduColumn));
306 // The last part will be added later
308 // Remove the parts from the database
309 mResolver.delete(mRawUri, where.toString(), whereArgs);
310 } catch (SQLException e) {
311 Log.e(TAG, "Can't access multipart SMS database", e);
312 return Intents.RESULT_SMS_GENERIC_ERROR;
314 if (cursor != null) cursor.close();
317 // Build up the data stream
318 ByteArrayOutputStream output = new ByteArrayOutputStream();
319 for (int i = 0; i < totalSegments; i++) {
320 // reassemble the (WSP-)pdu
322 // This one isn't in the DB, so add it
323 output.write(pdu, index, pdu.length - index);
325 output.write(pdus[i], 0, pdus[i].length);
329 byte[] datagram = output.toByteArray();
330 // Dispatch the PDU to applications
331 switch (destinationPort) {
332 case SmsHeader.PORT_WAP_PUSH:
334 return mWapPush.dispatchWapPdu(datagram);
337 pdus = new byte[1][];
339 // The messages were sent to any other WAP port
340 dispatchPortAddressedPdus(pdus, destinationPort);
341 return Activity.RESULT_OK;
348 protected void sendData(String destAddr, String scAddr, int destPort,
349 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
350 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
351 scAddr, destAddr, destPort, data, (deliveryIntent != null));
352 sendSubmitPdu(pdu, sentIntent, deliveryIntent);
357 protected void sendText(String destAddr, String scAddr, String text,
358 PendingIntent sentIntent, PendingIntent deliveryIntent) {
359 SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
360 scAddr, destAddr, text, (deliveryIntent != null), null);
361 sendSubmitPdu(pdu, sentIntent, deliveryIntent);
366 protected void sendMultipartText(String destAddr, String scAddr,
367 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
368 ArrayList<PendingIntent> deliveryIntents) {
371 * TODO(cleanup): There is no real code difference between
372 * this and the GSM version, and hence it should be moved to
373 * the base class or consolidated somehow, provided calling
374 * the proper submit pdu stuff can be arranged.
377 int refNumber = getNextConcatenatedRef() & 0x00FF;
378 int msgCount = parts.size();
379 int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
381 for (int i = 0; i < msgCount; i++) {
382 TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
383 if (encoding != details.codeUnitSize
384 && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
385 || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
386 encoding = details.codeUnitSize;
390 for (int i = 0; i < msgCount; i++) {
391 SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
392 concatRef.refNumber = refNumber;
393 concatRef.seqNumber = i + 1; // 1-based sequence
394 concatRef.msgCount = msgCount;
395 concatRef.isEightBits = true;
396 SmsHeader smsHeader = new SmsHeader();
397 smsHeader.concatRef = concatRef;
399 PendingIntent sentIntent = null;
400 if (sentIntents != null && sentIntents.size() > i) {
401 sentIntent = sentIntents.get(i);
404 PendingIntent deliveryIntent = null;
405 if (deliveryIntents != null && deliveryIntents.size() > i) {
406 deliveryIntent = deliveryIntents.get(i);
409 UserData uData = new UserData();
410 uData.payloadStr = parts.get(i);
411 uData.userDataHeader = smsHeader;
412 if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
413 uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
414 } else { // assume UTF-16
415 uData.msgEncoding = UserData.ENCODING_UNICODE_16;
417 uData.msgEncodingSet = true;
419 /* By setting the statusReportRequested bit only for the
420 * last message fragment, this will result in only one
421 * callback to the sender when that last fragment delivery
422 * has been acknowledged. */
423 SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destAddr,
424 uData, (deliveryIntent != null) && (i == (msgCount - 1)));
426 sendSubmitPdu(submitPdu, sentIntent, deliveryIntent);
430 protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
431 PendingIntent sentIntent, PendingIntent deliveryIntent) {
432 if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
433 if (sentIntent != null) {
435 sentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
436 } catch (CanceledException ex) {}
439 Log.d(TAG, "Block SMS in Emergency Callback mode");
443 sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
448 protected void sendSms(SmsTracker tracker) {
449 HashMap<String, Object> map = tracker.mData;
451 // byte smsc[] = (byte[]) map.get("smsc"); // unused for CDMA
452 byte pdu[] = (byte[]) map.get("pdu");
454 Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
456 mCm.sendCdmaSms(pdu, reply);
461 protected void sendMultipartSms (SmsTracker tracker) {
462 Log.d(TAG, "TODO: CdmaSMSDispatcher.sendMultipartSms not implemented");
467 protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
468 // FIXME unit test leaves cm == null. this should change
470 String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
471 if (inEcm.equals("true")) {
476 int causeCode = resultToCause(result);
477 mCm.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
479 if (causeCode == 0) {
480 mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
482 mLastDispatchedSmsFingerprint = null;
488 public void activateCellBroadcastSms(int activate, Message response) {
489 mCm.setCdmaBroadcastActivation((activate == 0), response);
494 public void getCellBroadcastSmsConfig(Message response) {
495 mCm.getCdmaBroadcastConfig(response);
500 public void setCellBroadcastConfig(int[] configValuesArray, Message response) {
501 mCm.setCdmaBroadcastConfig(configValuesArray, response);
504 protected void handleBroadcastSms(AsyncResult ar) {
506 Log.e(TAG, "Error! Not implemented for CDMA.");
509 private int resultToCause(int rc) {
511 case Activity.RESULT_OK:
512 case Intents.RESULT_SMS_HANDLED:
513 // Cause code is ignored on success.
515 case Intents.RESULT_SMS_OUT_OF_MEMORY:
516 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
517 case Intents.RESULT_SMS_UNSUPPORTED:
518 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
519 case Intents.RESULT_SMS_GENERIC_ERROR:
521 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;