Home | History | Annotate | Download | only in cdma
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      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
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     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.
     15  */
     16 
     17 package com.android.internal.telephony.cdma;
     18 
     19 
     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.Message;
     29 import android.os.SystemProperties;
     30 import android.preference.PreferenceManager;
     31 import android.provider.Telephony;
     32 import android.provider.Telephony.Sms.Intents;
     33 import android.telephony.SmsManager;
     34 import android.telephony.SmsMessage.MessageClass;
     35 import android.util.Log;
     36 
     37 import com.android.internal.telephony.CommandsInterface;
     38 import com.android.internal.telephony.SMSDispatcher;
     39 import com.android.internal.telephony.SmsHeader;
     40 import com.android.internal.telephony.SmsMessageBase;
     41 import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
     42 import com.android.internal.telephony.SmsStorageMonitor;
     43 import com.android.internal.telephony.SmsUsageMonitor;
     44 import com.android.internal.telephony.TelephonyProperties;
     45 import com.android.internal.telephony.WspTypeDecoder;
     46 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
     47 import com.android.internal.telephony.cdma.sms.UserData;
     48 import com.android.internal.util.HexDump;
     49 
     50 import java.io.ByteArrayOutputStream;
     51 import java.util.Arrays;
     52 import java.util.HashMap;
     53 
     54 import android.content.res.Resources;
     55 
     56 
     57 final class CdmaSMSDispatcher extends SMSDispatcher {
     58     private static final String TAG = "CDMA";
     59 
     60     private byte[] mLastDispatchedSmsFingerprint;
     61     private byte[] mLastAcknowledgedSmsFingerprint;
     62 
     63     private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
     64             com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
     65 
     66     CdmaSMSDispatcher(CDMAPhone phone, SmsStorageMonitor storageMonitor,
     67             SmsUsageMonitor usageMonitor) {
     68         super(phone, storageMonitor, usageMonitor);
     69         mCm.setOnNewCdmaSms(this, EVENT_NEW_SMS, null);
     70     }
     71 
     72     @Override
     73     public void dispose() {
     74         mCm.unSetOnNewCdmaSms(this);
     75     }
     76 
     77     @Override
     78     protected String getFormat() {
     79         return android.telephony.SmsMessage.FORMAT_3GPP2;
     80     }
     81 
     82     private void handleCdmaStatusReport(SmsMessage sms) {
     83         for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
     84             SmsTracker tracker = deliveryPendingList.get(i);
     85             if (tracker.mMessageRef == sms.messageRef) {
     86                 // Found it.  Remove from list and broadcast.
     87                 deliveryPendingList.remove(i);
     88                 PendingIntent intent = tracker.mDeliveryIntent;
     89                 Intent fillIn = new Intent();
     90                 fillIn.putExtra("pdu", sms.getPdu());
     91                 fillIn.putExtra("format", android.telephony.SmsMessage.FORMAT_3GPP2);
     92                 try {
     93                     intent.send(mContext, Activity.RESULT_OK, fillIn);
     94                 } catch (CanceledException ex) {}
     95                 break;  // Only expect to see one tracker matching this message.
     96             }
     97         }
     98     }
     99 
    100     /** {@inheritDoc} */
    101     @Override
    102     public int dispatchMessage(SmsMessageBase smsb) {
    103 
    104         // If sms is null, means there was a parsing error.
    105         if (smsb == null) {
    106             Log.e(TAG, "dispatchMessage: message is null");
    107             return Intents.RESULT_SMS_GENERIC_ERROR;
    108         }
    109 
    110         String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
    111         if (inEcm.equals("true")) {
    112             return Activity.RESULT_OK;
    113         }
    114 
    115         if (mSmsReceiveDisabled) {
    116             // Device doesn't support receiving SMS,
    117             Log.d(TAG, "Received short message on device which doesn't support "
    118                     + "receiving SMS. Ignored.");
    119             return Intents.RESULT_SMS_HANDLED;
    120         }
    121 
    122         // See if we have a network duplicate SMS.
    123         SmsMessage sms = (SmsMessage) smsb;
    124         mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
    125         if (mLastAcknowledgedSmsFingerprint != null &&
    126                 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
    127             return Intents.RESULT_SMS_HANDLED;
    128         }
    129         // Decode BD stream and set sms variables.
    130         sms.parseSms();
    131         int teleService = sms.getTeleService();
    132         boolean handled = false;
    133 
    134         if ((SmsEnvelope.TELESERVICE_VMN == teleService) ||
    135                 (SmsEnvelope.TELESERVICE_MWI == teleService)) {
    136             // handling Voicemail
    137             int voicemailCount = sms.getNumOfVoicemails();
    138             Log.d(TAG, "Voicemail count=" + voicemailCount);
    139             // Store the voicemail count in preferences.
    140             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
    141                     mContext);
    142             SharedPreferences.Editor editor = sp.edit();
    143             editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
    144             editor.apply();
    145             mPhone.setVoiceMessageWaiting(1, voicemailCount);
    146             handled = true;
    147         } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
    148                 (SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
    149                 sms.isStatusReportMessage()) {
    150             handleCdmaStatusReport(sms);
    151             handled = true;
    152         } else if ((sms.getUserData() == null)) {
    153             if (false) {
    154                 Log.d(TAG, "Received SMS without user data");
    155             }
    156             handled = true;
    157         }
    158 
    159         if (handled) {
    160             return Intents.RESULT_SMS_HANDLED;
    161         }
    162 
    163         if (!mStorageMonitor.isStorageAvailable() &&
    164                 sms.getMessageClass() != MessageClass.CLASS_0) {
    165             // It's a storable message and there's no storage available.  Bail.
    166             // (See C.S0015-B v2.0 for a description of "Immediate Display"
    167             // messages, which we represent as CLASS_0.)
    168             return Intents.RESULT_SMS_OUT_OF_MEMORY;
    169         }
    170 
    171         if (SmsEnvelope.TELESERVICE_WAP == teleService) {
    172             return processCdmaWapPdu(sms.getUserData(), sms.messageRef,
    173                     sms.getOriginatingAddress());
    174         }
    175 
    176         // Reject (NAK) any messages with teleservice ids that have
    177         // not yet been handled and also do not correspond to the two
    178         // kinds that are processed below.
    179         if ((SmsEnvelope.TELESERVICE_WMT != teleService) &&
    180                 (SmsEnvelope.TELESERVICE_WEMT != teleService) &&
    181                 (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) {
    182             return Intents.RESULT_SMS_UNSUPPORTED;
    183         }
    184 
    185         return dispatchNormalMessage(smsb);
    186     }
    187 
    188     /**
    189      * Processes inbound messages that are in the WAP-WDP PDU format. See
    190      * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
    191      * WDP segments are gathered until a datagram completes and gets dispatched.
    192      *
    193      * @param pdu The WAP-WDP PDU segment
    194      * @return a result code from {@link Telephony.Sms.Intents}, or
    195      *         {@link Activity#RESULT_OK} if the message has been broadcast
    196      *         to applications
    197      */
    198     protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
    199         int index = 0;
    200 
    201         int msgType = (0xFF & pdu[index++]);
    202         if (msgType != 0) {
    203             Log.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
    204             return Intents.RESULT_SMS_HANDLED;
    205         }
    206         int totalSegments = (0xFF & pdu[index++]);   // >= 1
    207         int segment = (0xFF & pdu[index++]);         // >= 0
    208 
    209         if (segment >= totalSegments) {
    210             Log.e(TAG, "WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1));
    211             return Intents.RESULT_SMS_HANDLED;
    212         }
    213 
    214         // Only the first segment contains sourcePort and destination Port
    215         int sourcePort = 0;
    216         int destinationPort = 0;
    217         if (segment == 0) {
    218             //process WDP segment
    219             sourcePort = (0xFF & pdu[index++]) << 8;
    220             sourcePort |= 0xFF & pdu[index++];
    221             destinationPort = (0xFF & pdu[index++]) << 8;
    222             destinationPort |= 0xFF & pdu[index++];
    223             // Some carriers incorrectly send duplicate port fields in omadm wap pushes.
    224             // If configured, check for that here
    225             if (mCheckForDuplicatePortsInOmadmWapPush) {
    226                 if (checkDuplicatePortOmadmWappush(pdu,index)) {
    227                     index = index + 4; // skip duplicate port fields
    228                 }
    229             }
    230         }
    231 
    232         // Lookup all other related parts
    233         Log.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
    234                 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
    235                 + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
    236 
    237         // pass the user data portion of the PDU to the shared handler in SMSDispatcher
    238         byte[] userData = new byte[pdu.length - index];
    239         System.arraycopy(pdu, index, userData, 0, pdu.length - index);
    240 
    241         return processMessagePart(userData, address, referenceNumber, segment, totalSegments,
    242                 0L, destinationPort, true);
    243     }
    244 
    245     /** {@inheritDoc} */
    246     @Override
    247     protected void sendData(String destAddr, String scAddr, int destPort,
    248             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
    249         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
    250                 scAddr, destAddr, destPort, data, (deliveryIntent != null));
    251         sendSubmitPdu(pdu, sentIntent, deliveryIntent);
    252     }
    253 
    254     /** {@inheritDoc} */
    255     @Override
    256     protected void sendText(String destAddr, String scAddr, String text,
    257             PendingIntent sentIntent, PendingIntent deliveryIntent) {
    258         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
    259                 scAddr, destAddr, text, (deliveryIntent != null), null);
    260         sendSubmitPdu(pdu, sentIntent, deliveryIntent);
    261     }
    262 
    263     /** {@inheritDoc} */
    264     @Override
    265     protected TextEncodingDetails calculateLength(CharSequence messageBody,
    266             boolean use7bitOnly) {
    267         return SmsMessage.calculateLength(messageBody, use7bitOnly);
    268     }
    269 
    270     /** {@inheritDoc} */
    271     @Override
    272     protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
    273             String message, SmsHeader smsHeader, int encoding,
    274             PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
    275         UserData uData = new UserData();
    276         uData.payloadStr = message;
    277         uData.userDataHeader = smsHeader;
    278         if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
    279             uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
    280         } else { // assume UTF-16
    281             uData.msgEncoding = UserData.ENCODING_UNICODE_16;
    282         }
    283         uData.msgEncodingSet = true;
    284 
    285         /* By setting the statusReportRequested bit only for the
    286          * last message fragment, this will result in only one
    287          * callback to the sender when that last fragment delivery
    288          * has been acknowledged. */
    289         SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
    290                 uData, (deliveryIntent != null) && lastPart);
    291 
    292         sendSubmitPdu(submitPdu, sentIntent, deliveryIntent);
    293     }
    294 
    295     protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
    296             PendingIntent sentIntent, PendingIntent deliveryIntent) {
    297         if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
    298             if (sentIntent != null) {
    299                 try {
    300                     sentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
    301                 } catch (CanceledException ex) {}
    302             }
    303             if (false) {
    304                 Log.d(TAG, "Block SMS in Emergency Callback mode");
    305             }
    306             return;
    307         }
    308         sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
    309     }
    310 
    311     /** {@inheritDoc} */
    312     @Override
    313     protected void sendSms(SmsTracker tracker) {
    314         HashMap<String, Object> map = tracker.mData;
    315 
    316         // byte smsc[] = (byte[]) map.get("smsc");  // unused for CDMA
    317         byte pdu[] = (byte[]) map.get("pdu");
    318 
    319         Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
    320         mCm.sendCdmaSms(pdu, reply);
    321     }
    322 
    323     /** {@inheritDoc} */
    324     @Override
    325     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
    326         String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
    327         if (inEcm.equals("true")) {
    328             return;
    329         }
    330 
    331         int causeCode = resultToCause(result);
    332         mCm.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
    333 
    334         if (causeCode == 0) {
    335             mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
    336         }
    337         mLastDispatchedSmsFingerprint = null;
    338     }
    339 
    340     private static int resultToCause(int rc) {
    341         switch (rc) {
    342         case Activity.RESULT_OK:
    343         case Intents.RESULT_SMS_HANDLED:
    344             // Cause code is ignored on success.
    345             return 0;
    346         case Intents.RESULT_SMS_OUT_OF_MEMORY:
    347             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
    348         case Intents.RESULT_SMS_UNSUPPORTED:
    349             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
    350         case Intents.RESULT_SMS_GENERIC_ERROR:
    351         default:
    352             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
    353         }
    354     }
    355 
    356     /**
    357      * Optional check to see if the received WapPush is an OMADM notification with erroneous
    358      * extra port fields.
    359      * - Some carriers make this mistake.
    360      * ex: MSGTYPE-TotalSegments-CurrentSegment
    361      *       -SourcePortDestPort-SourcePortDestPort-OMADM PDU
    362      * @param origPdu The WAP-WDP PDU segment
    363      * @param index Current Index while parsing the PDU.
    364      * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
    365      *         False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
    366      */
    367     private static boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) {
    368         index += 4;
    369         byte[] omaPdu = new byte[origPdu.length - index];
    370         System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
    371 
    372         WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
    373         int wspIndex = 2;
    374 
    375         // Process header length field
    376         if (pduDecoder.decodeUintvarInteger(wspIndex) == false) {
    377             return false;
    378         }
    379 
    380         wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field
    381 
    382         // Process content type field
    383         if (pduDecoder.decodeContentType(wspIndex) == false) {
    384             return false;
    385         }
    386 
    387         String mimeType = pduDecoder.getValueString();
    388         if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI)) {
    389             return true;
    390         }
    391         return false;
    392     }
    393 }
    394