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.AppOpsManager;
     22 import android.app.PendingIntent;
     23 import android.app.PendingIntent.CanceledException;
     24 import android.content.BroadcastReceiver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.SharedPreferences;
     28 import android.content.res.Resources;
     29 import android.os.Bundle;
     30 import android.os.Message;
     31 import android.os.SystemProperties;
     32 import android.preference.PreferenceManager;
     33 import android.provider.Telephony.Sms.Intents;
     34 import android.telephony.PhoneNumberUtils;
     35 import android.telephony.SmsCbMessage;
     36 import android.telephony.SmsManager;
     37 import android.telephony.cdma.CdmaSmsCbProgramData;
     38 import android.telephony.cdma.CdmaSmsCbProgramResults;
     39 import android.telephony.Rlog;
     40 
     41 import com.android.internal.telephony.CommandsInterface;
     42 import com.android.internal.telephony.GsmAlphabet;
     43 import com.android.internal.telephony.SmsConstants;
     44 import com.android.internal.telephony.SMSDispatcher;
     45 import com.android.internal.telephony.SmsHeader;
     46 import com.android.internal.telephony.SmsMessageBase;
     47 import com.android.internal.telephony.SmsStorageMonitor;
     48 import com.android.internal.telephony.SmsUsageMonitor;
     49 import com.android.internal.telephony.TelephonyProperties;
     50 import com.android.internal.telephony.WspTypeDecoder;
     51 import com.android.internal.telephony.cdma.sms.BearerData;
     52 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
     53 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
     54 import com.android.internal.telephony.cdma.sms.UserData;
     55 
     56 import java.io.ByteArrayOutputStream;
     57 import java.io.DataOutputStream;
     58 import java.io.IOException;
     59 import java.util.ArrayList;
     60 import java.util.Arrays;
     61 import java.util.HashMap;
     62 
     63 
     64 final class CdmaSMSDispatcher extends SMSDispatcher {
     65     private static final String TAG = "CdmaSMSDispatcher";
     66     private static final boolean VDBG = false;
     67 
     68     private byte[] mLastDispatchedSmsFingerprint;
     69     private byte[] mLastAcknowledgedSmsFingerprint;
     70 
     71     private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
     72             com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
     73 
     74     CdmaSMSDispatcher(CDMAPhone phone, SmsStorageMonitor storageMonitor,
     75             SmsUsageMonitor usageMonitor) {
     76         super(phone, storageMonitor, usageMonitor);
     77         mCi.setOnNewCdmaSms(this, EVENT_NEW_SMS, null);
     78     }
     79 
     80     @Override
     81     public void dispose() {
     82         mCi.unSetOnNewCdmaSms(this);
     83     }
     84 
     85     @Override
     86     protected String getFormat() {
     87         return android.telephony.SmsMessage.FORMAT_3GPP2;
     88     }
     89 
     90     private void handleCdmaStatusReport(SmsMessage sms) {
     91         for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
     92             SmsTracker tracker = deliveryPendingList.get(i);
     93             if (tracker.mMessageRef == sms.mMessageRef) {
     94                 // Found it.  Remove from list and broadcast.
     95                 deliveryPendingList.remove(i);
     96                 PendingIntent intent = tracker.mDeliveryIntent;
     97                 Intent fillIn = new Intent();
     98                 fillIn.putExtra("pdu", sms.getPdu());
     99                 fillIn.putExtra("format", android.telephony.SmsMessage.FORMAT_3GPP2);
    100                 try {
    101                     intent.send(mContext, Activity.RESULT_OK, fillIn);
    102                 } catch (CanceledException ex) {}
    103                 break;  // Only expect to see one tracker matching this message.
    104             }
    105         }
    106     }
    107 
    108     /**
    109      * Dispatch service category program data to the CellBroadcastReceiver app, which filters
    110      * the broadcast alerts to display.
    111      * @param sms the SMS message containing one or more
    112      * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects.
    113      */
    114     private void handleServiceCategoryProgramData(SmsMessage sms) {
    115         ArrayList<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData();
    116         if (programDataList == null) {
    117             Rlog.e(TAG, "handleServiceCategoryProgramData: program data list is null!");
    118             return;
    119         }
    120 
    121         Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION);
    122         intent.putExtra("sender", sms.getOriginatingAddress());
    123         intent.putParcelableArrayListExtra("program_data", programDataList);
    124         dispatch(intent, RECEIVE_SMS_PERMISSION, AppOpsManager.OP_RECEIVE_SMS, mScpResultsReceiver);
    125     }
    126 
    127     /** {@inheritDoc} */
    128     @Override
    129     public int dispatchMessage(SmsMessageBase smsb) {
    130 
    131         // If sms is null, means there was a parsing error.
    132         if (smsb == null) {
    133             Rlog.e(TAG, "dispatchMessage: message is null");
    134             return Intents.RESULT_SMS_GENERIC_ERROR;
    135         }
    136 
    137         String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
    138         if (inEcm.equals("true")) {
    139             return Activity.RESULT_OK;
    140         }
    141 
    142         if (mSmsReceiveDisabled) {
    143             // Device doesn't support receiving SMS,
    144             Rlog.d(TAG, "Received short message on device which doesn't support "
    145                     + "receiving SMS. Ignored.");
    146             return Intents.RESULT_SMS_HANDLED;
    147         }
    148 
    149         SmsMessage sms = (SmsMessage) smsb;
    150 
    151         // Handle CMAS emergency broadcast messages.
    152         if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) {
    153             Rlog.d(TAG, "Broadcast type message");
    154             SmsCbMessage message = sms.parseBroadcastSms();
    155             if (message != null) {
    156                 dispatchBroadcastMessage(message);
    157             }
    158             return Intents.RESULT_SMS_HANDLED;
    159         }
    160 
    161         // See if we have a network duplicate SMS.
    162         mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
    163         if (mLastAcknowledgedSmsFingerprint != null &&
    164                 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
    165             return Intents.RESULT_SMS_HANDLED;
    166         }
    167         // Decode BD stream and set sms variables.
    168         sms.parseSms();
    169         int teleService = sms.getTeleService();
    170         boolean handled = false;
    171 
    172         if ((SmsEnvelope.TELESERVICE_VMN == teleService) ||
    173                 (SmsEnvelope.TELESERVICE_MWI == teleService)) {
    174             // handling Voicemail
    175             int voicemailCount = sms.getNumOfVoicemails();
    176             Rlog.d(TAG, "Voicemail count=" + voicemailCount);
    177             // Store the voicemail count in preferences.
    178             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
    179                     mContext);
    180             SharedPreferences.Editor editor = sp.edit();
    181             editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
    182             editor.apply();
    183             mPhone.setVoiceMessageWaiting(1, voicemailCount);
    184             handled = true;
    185         } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
    186                 (SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
    187                 sms.isStatusReportMessage()) {
    188             handleCdmaStatusReport(sms);
    189             handled = true;
    190         } else if (SmsEnvelope.TELESERVICE_SCPT == teleService) {
    191             handleServiceCategoryProgramData(sms);
    192             handled = true;
    193         } else if ((sms.getUserData() == null)) {
    194             if (VDBG) {
    195                 Rlog.d(TAG, "Received SMS without user data");
    196             }
    197             handled = true;
    198         }
    199 
    200         if (handled) {
    201             return Intents.RESULT_SMS_HANDLED;
    202         }
    203 
    204         if (!mStorageMonitor.isStorageAvailable() &&
    205                 sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
    206             // It's a storable message and there's no storage available.  Bail.
    207             // (See C.S0015-B v2.0 for a description of "Immediate Display"
    208             // messages, which we represent as CLASS_0.)
    209             return Intents.RESULT_SMS_OUT_OF_MEMORY;
    210         }
    211 
    212         if (SmsEnvelope.TELESERVICE_WAP == teleService) {
    213             return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef,
    214                     sms.getOriginatingAddress());
    215         }
    216 
    217         // Reject (NAK) any messages with teleservice ids that have
    218         // not yet been handled and also do not correspond to the two
    219         // kinds that are processed below.
    220         if ((SmsEnvelope.TELESERVICE_WMT != teleService) &&
    221                 (SmsEnvelope.TELESERVICE_WEMT != teleService) &&
    222                 (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) {
    223             return Intents.RESULT_SMS_UNSUPPORTED;
    224         }
    225 
    226         return dispatchNormalMessage(smsb);
    227     }
    228 
    229     /**
    230      * Processes inbound messages that are in the WAP-WDP PDU format. See
    231      * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
    232      * WDP segments are gathered until a datagram completes and gets dispatched.
    233      *
    234      * @param pdu The WAP-WDP PDU segment
    235      * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
    236      *         {@link Activity#RESULT_OK} if the message has been broadcast
    237      *         to applications
    238      */
    239     protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
    240         int index = 0;
    241 
    242         int msgType = (0xFF & pdu[index++]);
    243         if (msgType != 0) {
    244             Rlog.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
    245             return Intents.RESULT_SMS_HANDLED;
    246         }
    247         int totalSegments = (0xFF & pdu[index++]);   // >= 1
    248         int segment = (0xFF & pdu[index++]);         // >= 0
    249 
    250         if (segment >= totalSegments) {
    251             Rlog.e(TAG, "WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1));
    252             return Intents.RESULT_SMS_HANDLED;
    253         }
    254 
    255         // Only the first segment contains sourcePort and destination Port
    256         int sourcePort = 0;
    257         int destinationPort = 0;
    258         if (segment == 0) {
    259             //process WDP segment
    260             sourcePort = (0xFF & pdu[index++]) << 8;
    261             sourcePort |= 0xFF & pdu[index++];
    262             destinationPort = (0xFF & pdu[index++]) << 8;
    263             destinationPort |= 0xFF & pdu[index++];
    264             // Some carriers incorrectly send duplicate port fields in omadm wap pushes.
    265             // If configured, check for that here
    266             if (mCheckForDuplicatePortsInOmadmWapPush) {
    267                 if (checkDuplicatePortOmadmWappush(pdu,index)) {
    268                     index = index + 4; // skip duplicate port fields
    269                 }
    270             }
    271         }
    272 
    273         // Lookup all other related parts
    274         Rlog.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
    275                 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
    276                 + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
    277 
    278         // pass the user data portion of the PDU to the shared handler in SMSDispatcher
    279         byte[] userData = new byte[pdu.length - index];
    280         System.arraycopy(pdu, index, userData, 0, pdu.length - index);
    281 
    282         return processMessagePart(userData, address, referenceNumber, segment, totalSegments,
    283                 0L, destinationPort, true);
    284     }
    285 
    286     /** {@inheritDoc} */
    287     @Override
    288     protected void sendData(String destAddr, String scAddr, int destPort,
    289             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
    290         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
    291                 scAddr, destAddr, destPort, data, (deliveryIntent != null));
    292         sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
    293     }
    294 
    295     /** {@inheritDoc} */
    296     @Override
    297     protected void sendText(String destAddr, String scAddr, String text,
    298             PendingIntent sentIntent, PendingIntent deliveryIntent) {
    299         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
    300                 scAddr, destAddr, text, (deliveryIntent != null), null);
    301         sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
    302     }
    303 
    304     /** {@inheritDoc} */
    305     @Override
    306     protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
    307             boolean use7bitOnly) {
    308         return SmsMessage.calculateLength(messageBody, use7bitOnly);
    309     }
    310 
    311     /** {@inheritDoc} */
    312     @Override
    313     protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
    314             String message, SmsHeader smsHeader, int encoding,
    315             PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
    316         UserData uData = new UserData();
    317         uData.payloadStr = message;
    318         uData.userDataHeader = smsHeader;
    319         if (encoding == SmsConstants.ENCODING_7BIT) {
    320             uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
    321         } else { // assume UTF-16
    322             uData.msgEncoding = UserData.ENCODING_UNICODE_16;
    323         }
    324         uData.msgEncodingSet = true;
    325 
    326         /* By setting the statusReportRequested bit only for the
    327          * last message fragment, this will result in only one
    328          * callback to the sender when that last fragment delivery
    329          * has been acknowledged. */
    330         SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
    331                 uData, (deliveryIntent != null) && lastPart);
    332 
    333         sendSubmitPdu(submitPdu, sentIntent, deliveryIntent, destinationAddress);
    334     }
    335 
    336     protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
    337             PendingIntent sentIntent, PendingIntent deliveryIntent, String destAddr) {
    338         if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
    339             if (sentIntent != null) {
    340                 try {
    341                     sentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
    342                 } catch (CanceledException ex) {}
    343             }
    344             if (VDBG) {
    345                 Rlog.d(TAG, "Block SMS in Emergency Callback mode");
    346             }
    347             return;
    348         }
    349         sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, destAddr);
    350     }
    351 
    352     /** {@inheritDoc} */
    353     @Override
    354     protected void sendSms(SmsTracker tracker) {
    355         HashMap<String, Object> map = tracker.mData;
    356 
    357         // byte smsc[] = (byte[]) map.get("smsc");  // unused for CDMA
    358         byte pdu[] = (byte[]) map.get("pdu");
    359 
    360         Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
    361         mCi.sendCdmaSms(pdu, reply);
    362     }
    363 
    364     /** {@inheritDoc} */
    365     @Override
    366     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
    367         String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
    368         if (inEcm.equals("true")) {
    369             return;
    370         }
    371 
    372         int causeCode = resultToCause(result);
    373         mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
    374 
    375         if (causeCode == 0) {
    376             mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
    377         }
    378         mLastDispatchedSmsFingerprint = null;
    379     }
    380 
    381     private static int resultToCause(int rc) {
    382         switch (rc) {
    383         case Activity.RESULT_OK:
    384         case Intents.RESULT_SMS_HANDLED:
    385             // Cause code is ignored on success.
    386             return 0;
    387         case Intents.RESULT_SMS_OUT_OF_MEMORY:
    388             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
    389         case Intents.RESULT_SMS_UNSUPPORTED:
    390             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
    391         case Intents.RESULT_SMS_GENERIC_ERROR:
    392         default:
    393             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
    394         }
    395     }
    396 
    397     /**
    398      * Optional check to see if the received WapPush is an OMADM notification with erroneous
    399      * extra port fields.
    400      * - Some carriers make this mistake.
    401      * ex: MSGTYPE-TotalSegments-CurrentSegment
    402      *       -SourcePortDestPort-SourcePortDestPort-OMADM PDU
    403      * @param origPdu The WAP-WDP PDU segment
    404      * @param index Current Index while parsing the PDU.
    405      * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
    406      *         False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
    407      */
    408     private static boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) {
    409         index += 4;
    410         byte[] omaPdu = new byte[origPdu.length - index];
    411         System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
    412 
    413         WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
    414         int wspIndex = 2;
    415 
    416         // Process header length field
    417         if (pduDecoder.decodeUintvarInteger(wspIndex) == false) {
    418             return false;
    419         }
    420 
    421         wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field
    422 
    423         // Process content type field
    424         if (pduDecoder.decodeContentType(wspIndex) == false) {
    425             return false;
    426         }
    427 
    428         String mimeType = pduDecoder.getValueString();
    429         if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI)) {
    430             return true;
    431         }
    432         return false;
    433     }
    434 
    435     // Receiver for Service Category Program Data results.
    436     // We already ACK'd the original SCPD SMS, so this sends a new response SMS.
    437     // TODO: handle retries if the RIL returns an error.
    438     private final BroadcastReceiver mScpResultsReceiver = new BroadcastReceiver() {
    439         @Override
    440         public void onReceive(Context context, Intent intent) {
    441             int rc = getResultCode();
    442             boolean success = (rc == Activity.RESULT_OK) || (rc == Intents.RESULT_SMS_HANDLED);
    443             if (!success) {
    444                 Rlog.e(TAG, "SCP results error: result code = " + rc);
    445                 return;
    446             }
    447             Bundle extras = getResultExtras(false);
    448             if (extras == null) {
    449                 Rlog.e(TAG, "SCP results error: missing extras");
    450                 return;
    451             }
    452             String sender = extras.getString("sender");
    453             if (sender == null) {
    454                 Rlog.e(TAG, "SCP results error: missing sender extra.");
    455                 return;
    456             }
    457             ArrayList<CdmaSmsCbProgramResults> results
    458                     = extras.getParcelableArrayList("results");
    459             if (results == null) {
    460                 Rlog.e(TAG, "SCP results error: missing results extra.");
    461                 return;
    462             }
    463 
    464             BearerData bData = new BearerData();
    465             bData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
    466             bData.messageId = SmsMessage.getNextMessageId();
    467             bData.serviceCategoryProgramResults = results;
    468             byte[] encodedBearerData = BearerData.encode(bData);
    469 
    470             ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
    471             DataOutputStream dos = new DataOutputStream(baos);
    472             try {
    473                 dos.writeInt(SmsEnvelope.TELESERVICE_SCPT);
    474                 dos.writeInt(0); //servicePresent
    475                 dos.writeInt(0); //serviceCategory
    476                 CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
    477                         PhoneNumberUtils.cdmaCheckAndProcessPlusCode(sender));
    478                 dos.write(destAddr.digitMode);
    479                 dos.write(destAddr.numberMode);
    480                 dos.write(destAddr.ton); // number_type
    481                 dos.write(destAddr.numberPlan);
    482                 dos.write(destAddr.numberOfDigits);
    483                 dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
    484                 // Subaddress is not supported.
    485                 dos.write(0); //subaddressType
    486                 dos.write(0); //subaddr_odd
    487                 dos.write(0); //subaddr_nbr_of_digits
    488                 dos.write(encodedBearerData.length);
    489                 dos.write(encodedBearerData, 0, encodedBearerData.length);
    490                 // Ignore the RIL response. TODO: implement retry if SMS send fails.
    491                 mCi.sendCdmaSms(baos.toByteArray(), null);
    492             } catch (IOException e) {
    493                 Rlog.e(TAG, "exception creating SCP results PDU", e);
    494             } finally {
    495                 try {
    496                     dos.close();
    497                 } catch (IOException ignored) {
    498                 }
    499             }
    500         }
    501     };
    502 }
    503