Home | History | Annotate | Download | only in gsm
      1 /*
      2  * Copyright (C) 2006 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.gsm;
     18 
     19 import android.app.Activity;
     20 import android.app.PendingIntent;
     21 import android.app.PendingIntent.CanceledException;
     22 import android.content.Intent;
     23 import android.os.AsyncResult;
     24 import android.os.Message;
     25 import android.os.SystemProperties;
     26 import android.provider.Telephony.Sms;
     27 import android.provider.Telephony.Sms.Intents;
     28 import android.telephony.PhoneNumberUtils;
     29 import android.telephony.SmsCbLocation;
     30 import android.telephony.SmsCbMessage;
     31 import android.telephony.SmsManager;
     32 import android.telephony.gsm.GsmCellLocation;
     33 import android.telephony.Rlog;
     34 
     35 import com.android.internal.telephony.CommandsInterface;
     36 import com.android.internal.telephony.GsmAlphabet;
     37 import com.android.internal.telephony.PhoneBase;
     38 import com.android.internal.telephony.SmsConstants;
     39 import com.android.internal.telephony.SMSDispatcher;
     40 import com.android.internal.telephony.SmsHeader;
     41 import com.android.internal.telephony.SmsMessageBase;
     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.uicc.IccUtils;
     46 import com.android.internal.telephony.uicc.UsimServiceTable;
     47 
     48 import java.util.HashMap;
     49 import java.util.Iterator;
     50 
     51 public final class GsmSMSDispatcher extends SMSDispatcher {
     52     private static final String TAG = "GsmSMSDispatcher";
     53     private static final boolean VDBG = false;
     54 
     55     /** Status report received */
     56     private static final int EVENT_NEW_SMS_STATUS_REPORT = 100;
     57 
     58     /** New broadcast SMS */
     59     private static final int EVENT_NEW_BROADCAST_SMS = 101;
     60 
     61     /** Result of writing SM to UICC (when SMS-PP service is not available). */
     62     private static final int EVENT_WRITE_SMS_COMPLETE = 102;
     63 
     64     /** Handler for SMS-PP data download messages to UICC. */
     65     private final UsimDataDownloadHandler mDataDownloadHandler;
     66 
     67     public GsmSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
     68             SmsUsageMonitor usageMonitor) {
     69         super(phone, storageMonitor, usageMonitor);
     70         mDataDownloadHandler = new UsimDataDownloadHandler(mCi);
     71         mCi.setOnNewGsmSms(this, EVENT_NEW_SMS, null);
     72         mCi.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
     73         mCi.setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null);
     74     }
     75 
     76     @Override
     77     public void dispose() {
     78         mCi.unSetOnNewGsmSms(this);
     79         mCi.unSetOnSmsStatus(this);
     80         mCi.unSetOnNewGsmBroadcastSms(this);
     81     }
     82 
     83     @Override
     84     protected String getFormat() {
     85         return SmsConstants.FORMAT_3GPP;
     86     }
     87 
     88     /**
     89      * Handles 3GPP format-specific events coming from the phone stack.
     90      * Other events are handled by {@link SMSDispatcher#handleMessage}.
     91      *
     92      * @param msg the message to handle
     93      */
     94     @Override
     95     public void handleMessage(Message msg) {
     96         switch (msg.what) {
     97         case EVENT_NEW_SMS_STATUS_REPORT:
     98             handleStatusReport((AsyncResult) msg.obj);
     99             break;
    100 
    101         case EVENT_NEW_BROADCAST_SMS:
    102             handleBroadcastSms((AsyncResult)msg.obj);
    103             break;
    104 
    105         case EVENT_WRITE_SMS_COMPLETE:
    106             AsyncResult ar = (AsyncResult) msg.obj;
    107             if (ar.exception == null) {
    108                 Rlog.d(TAG, "Successfully wrote SMS-PP message to UICC");
    109                 mCi.acknowledgeLastIncomingGsmSms(true, 0, null);
    110             } else {
    111                 Rlog.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception);
    112                 mCi.acknowledgeLastIncomingGsmSms(false,
    113                         CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null);
    114             }
    115             break;
    116 
    117         default:
    118             super.handleMessage(msg);
    119         }
    120     }
    121 
    122     /**
    123      * Called when a status report is received.  This should correspond to
    124      * a previously successful SEND.
    125      *
    126      * @param ar AsyncResult passed into the message handler.  ar.result should
    127      *           be a String representing the status report PDU, as ASCII hex.
    128      */
    129     private void handleStatusReport(AsyncResult ar) {
    130         String pduString = (String) ar.result;
    131         SmsMessage sms = SmsMessage.newFromCDS(pduString);
    132 
    133         if (sms != null) {
    134             int tpStatus = sms.getStatus();
    135             int messageRef = sms.mMessageRef;
    136             for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
    137                 SmsTracker tracker = deliveryPendingList.get(i);
    138                 if (tracker.mMessageRef == messageRef) {
    139                     // Found it.  Remove from list and broadcast.
    140                     if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) {
    141                        deliveryPendingList.remove(i);
    142                     }
    143                     PendingIntent intent = tracker.mDeliveryIntent;
    144                     Intent fillIn = new Intent();
    145                     fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString));
    146                     fillIn.putExtra("format", SmsConstants.FORMAT_3GPP);
    147                     try {
    148                         intent.send(mContext, Activity.RESULT_OK, fillIn);
    149                     } catch (CanceledException ex) {}
    150 
    151                     // Only expect to see one tracker matching this messageref
    152                     break;
    153                 }
    154             }
    155         }
    156         acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null);
    157     }
    158 
    159     /** {@inheritDoc} */
    160     @Override
    161     public int dispatchMessage(SmsMessageBase smsb) {
    162 
    163         // If sms is null, means there was a parsing error.
    164         if (smsb == null) {
    165             Rlog.e(TAG, "dispatchMessage: message is null");
    166             return Intents.RESULT_SMS_GENERIC_ERROR;
    167         }
    168 
    169         SmsMessage sms = (SmsMessage) smsb;
    170 
    171         if (sms.isTypeZero()) {
    172             // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
    173             // Displayed/Stored/Notified. They should only be acknowledged.
    174             Rlog.d(TAG, "Received short message type 0, Don't display or store it. Send Ack");
    175             return Intents.RESULT_SMS_HANDLED;
    176         }
    177 
    178         // Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1.
    179         if (sms.isUsimDataDownload()) {
    180             UsimServiceTable ust = mPhone.getUsimServiceTable();
    181             // If we receive an SMS-PP message before the UsimServiceTable has been loaded,
    182             // assume that the data download service is not present. This is very unlikely to
    183             // happen because the IMS connection will not be established until after the ISIM
    184             // records have been loaded, after the USIM service table has been loaded.
    185             if (ust != null && ust.isAvailable(
    186                     UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) {
    187                 Rlog.d(TAG, "Received SMS-PP data download, sending to UICC.");
    188                 return mDataDownloadHandler.startDataDownload(sms);
    189             } else {
    190                 Rlog.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC.");
    191                 String smsc = IccUtils.bytesToHexString(
    192                         PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
    193                                 sms.getServiceCenterAddress()));
    194                 mCi.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc,
    195                         IccUtils.bytesToHexString(sms.getPdu()),
    196                         obtainMessage(EVENT_WRITE_SMS_COMPLETE));
    197                 return Activity.RESULT_OK;  // acknowledge after response from write to USIM
    198             }
    199         }
    200 
    201         if (mSmsReceiveDisabled) {
    202             // Device doesn't support SMS service,
    203             Rlog.d(TAG, "Received short message on device which doesn't support "
    204                     + "SMS service. Ignored.");
    205             return Intents.RESULT_SMS_HANDLED;
    206         }
    207 
    208         // Special case the message waiting indicator messages
    209         boolean handled = false;
    210         if (sms.isMWISetMessage()) {
    211             mPhone.setVoiceMessageWaiting(1, -1);  // line 1: unknown number of msgs waiting
    212             handled = sms.isMwiDontStore();
    213             if (VDBG) {
    214                 Rlog.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
    215             }
    216         } else if (sms.isMWIClearMessage()) {
    217             mPhone.setVoiceMessageWaiting(1, 0);   // line 1: no msgs waiting
    218             handled = sms.isMwiDontStore();
    219             if (VDBG) {
    220                 Rlog.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
    221             }
    222         }
    223 
    224         if (handled) {
    225             return Intents.RESULT_SMS_HANDLED;
    226         }
    227 
    228         if (!mStorageMonitor.isStorageAvailable() &&
    229                 sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
    230             // It's a storable message and there's no storage available.  Bail.
    231             // (See TS 23.038 for a description of class 0 messages.)
    232             return Intents.RESULT_SMS_OUT_OF_MEMORY;
    233         }
    234 
    235         return dispatchNormalMessage(smsb);
    236     }
    237 
    238     /** {@inheritDoc} */
    239     @Override
    240     protected void sendData(String destAddr, String scAddr, int destPort,
    241             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
    242         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
    243                 scAddr, destAddr, destPort, data, (deliveryIntent != null));
    244         if (pdu != null) {
    245             sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
    246                     destAddr);
    247         } else {
    248             Rlog.e(TAG, "GsmSMSDispatcher.sendData(): getSubmitPdu() returned null");
    249         }
    250     }
    251 
    252     /** {@inheritDoc} */
    253     @Override
    254     protected void sendText(String destAddr, String scAddr, String text,
    255             PendingIntent sentIntent, PendingIntent deliveryIntent) {
    256         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
    257                 scAddr, destAddr, text, (deliveryIntent != null));
    258         if (pdu != null) {
    259             sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
    260                     destAddr);
    261         } else {
    262             Rlog.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
    263         }
    264     }
    265 
    266     /** {@inheritDoc} */
    267     @Override
    268     protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
    269             boolean use7bitOnly) {
    270         return SmsMessage.calculateLength(messageBody, use7bitOnly);
    271     }
    272 
    273     /** {@inheritDoc} */
    274     @Override
    275     protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
    276             String message, SmsHeader smsHeader, int encoding,
    277             PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
    278         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
    279                 message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
    280                 encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
    281         if (pdu != null) {
    282             sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
    283                     destinationAddress);
    284         } else {
    285             Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
    286         }
    287     }
    288 
    289     /** {@inheritDoc} */
    290     @Override
    291     protected void sendSms(SmsTracker tracker) {
    292         HashMap<String, Object> map = tracker.mData;
    293 
    294         byte smsc[] = (byte[]) map.get("smsc");
    295         byte pdu[] = (byte[]) map.get("pdu");
    296 
    297         Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
    298 
    299         if (tracker.mRetryCount > 0) {
    300             Rlog.d(TAG, "sendSms: "
    301                     + " mRetryCount=" + tracker.mRetryCount
    302                     + " mMessageRef=" + tracker.mMessageRef
    303                     + " SS=" + mPhone.getServiceState().getState());
    304 
    305             // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
    306             //   TP-RD (bit 2) is 1 for retry
    307             //   and TP-MR is set to previously failed sms TP-MR
    308             if (((0x01 & pdu[0]) == 0x01)) {
    309                 pdu[0] |= 0x04; // TP-RD
    310                 pdu[1] = (byte) tracker.mMessageRef; // TP-MR
    311             }
    312         }
    313         mCi.sendSMS(IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), reply);
    314     }
    315 
    316     /** {@inheritDoc} */
    317     @Override
    318     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
    319         mCi.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
    320     }
    321 
    322     private static int resultToCause(int rc) {
    323         switch (rc) {
    324             case Activity.RESULT_OK:
    325             case Intents.RESULT_SMS_HANDLED:
    326                 // Cause code is ignored on success.
    327                 return 0;
    328             case Intents.RESULT_SMS_OUT_OF_MEMORY:
    329                 return CommandsInterface.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED;
    330             case Intents.RESULT_SMS_GENERIC_ERROR:
    331             default:
    332                 return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR;
    333         }
    334     }
    335 
    336     /**
    337      * Holds all info about a message page needed to assemble a complete
    338      * concatenated message
    339      */
    340     private static final class SmsCbConcatInfo {
    341 
    342         private final SmsCbHeader mHeader;
    343         private final SmsCbLocation mLocation;
    344 
    345         public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
    346             mHeader = header;
    347             mLocation = location;
    348         }
    349 
    350         @Override
    351         public int hashCode() {
    352             return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
    353         }
    354 
    355         @Override
    356         public boolean equals(Object obj) {
    357             if (obj instanceof SmsCbConcatInfo) {
    358                 SmsCbConcatInfo other = (SmsCbConcatInfo)obj;
    359 
    360                 // Two pages match if they have the same serial number (which includes the
    361                 // geographical scope and update number), and both pages belong to the same
    362                 // location (PLMN, plus LAC and CID if these are part of the geographical scope).
    363                 return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
    364                         && mLocation.equals(other.mLocation);
    365             }
    366 
    367             return false;
    368         }
    369 
    370         /**
    371          * Compare the location code for this message to the current location code. The match is
    372          * relative to the geographical scope of the message, which determines whether the LAC
    373          * and Cell ID are saved in mLocation or set to -1 to match all values.
    374          *
    375          * @param plmn the current PLMN
    376          * @param lac the current Location Area (GSM) or Service Area (UMTS)
    377          * @param cid the current Cell ID
    378          * @return true if this message is valid for the current location; false otherwise
    379          */
    380         public boolean matchesLocation(String plmn, int lac, int cid) {
    381             return mLocation.isInLocationArea(plmn, lac, cid);
    382         }
    383     }
    384 
    385     // This map holds incomplete concatenated messages waiting for assembly
    386     private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
    387             new HashMap<SmsCbConcatInfo, byte[][]>();
    388 
    389     /**
    390      * Handle 3GPP format SMS-CB message.
    391      * @param ar the AsyncResult containing the received PDUs
    392      */
    393     private void handleBroadcastSms(AsyncResult ar) {
    394         try {
    395             byte[] receivedPdu = (byte[])ar.result;
    396 
    397             if (VDBG) {
    398                 for (int i = 0; i < receivedPdu.length; i += 8) {
    399                     StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
    400                     for (int j = i; j < i + 8 && j < receivedPdu.length; j++) {
    401                         int b = receivedPdu[j] & 0xff;
    402                         if (b < 0x10) {
    403                             sb.append('0');
    404                         }
    405                         sb.append(Integer.toHexString(b)).append(' ');
    406                     }
    407                     Rlog.d(TAG, sb.toString());
    408                 }
    409             }
    410 
    411             SmsCbHeader header = new SmsCbHeader(receivedPdu);
    412             String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
    413             int lac = -1;
    414             int cid = -1;
    415             android.telephony.CellLocation cl = mPhone.getCellLocation();
    416             // Check if cell location is GsmCellLocation.  This is required to support
    417             // dual-mode devices such as CDMA/LTE devices that require support for
    418             // both 3GPP and 3GPP2 format messages
    419             if (cl instanceof GsmCellLocation) {
    420                 GsmCellLocation cellLocation = (GsmCellLocation)cl;
    421                 lac = cellLocation.getLac();
    422                 cid = cellLocation.getCid();
    423             }
    424 
    425             SmsCbLocation location;
    426             switch (header.getGeographicalScope()) {
    427                 case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
    428                     location = new SmsCbLocation(plmn, lac, -1);
    429                     break;
    430 
    431                 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
    432                 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
    433                     location = new SmsCbLocation(plmn, lac, cid);
    434                     break;
    435 
    436                 case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
    437                 default:
    438                     location = new SmsCbLocation(plmn);
    439                     break;
    440             }
    441 
    442             byte[][] pdus;
    443             int pageCount = header.getNumberOfPages();
    444             if (pageCount > 1) {
    445                 // Multi-page message
    446                 SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
    447 
    448                 // Try to find other pages of the same message
    449                 pdus = mSmsCbPageMap.get(concatInfo);
    450 
    451                 if (pdus == null) {
    452                     // This is the first page of this message, make room for all
    453                     // pages and keep until complete
    454                     pdus = new byte[pageCount][];
    455 
    456                     mSmsCbPageMap.put(concatInfo, pdus);
    457                 }
    458 
    459                 // Page parameter is one-based
    460                 pdus[header.getPageIndex() - 1] = receivedPdu;
    461 
    462                 for (int i = 0; i < pdus.length; i++) {
    463                     if (pdus[i] == null) {
    464                         // Still missing pages, exit
    465                         return;
    466                     }
    467                 }
    468 
    469                 // Message complete, remove and dispatch
    470                 mSmsCbPageMap.remove(concatInfo);
    471             } else {
    472                 // Single page message
    473                 pdus = new byte[1][];
    474                 pdus[0] = receivedPdu;
    475             }
    476 
    477             SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus);
    478             dispatchBroadcastMessage(message);
    479 
    480             // Remove messages that are out of scope to prevent the map from
    481             // growing indefinitely, containing incomplete messages that were
    482             // never assembled
    483             Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();
    484 
    485             while (iter.hasNext()) {
    486                 SmsCbConcatInfo info = iter.next();
    487 
    488                 if (!info.matchesLocation(plmn, lac, cid)) {
    489                     iter.remove();
    490                 }
    491             }
    492         } catch (RuntimeException e) {
    493             Rlog.e(TAG, "Error in decoding SMS CB pdu", e);
    494         }
    495     }
    496 }
    497