Home | History | Annotate | Download | only in cdma
      1 /*
      2  * Copyright (C) 2013 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 import android.app.Activity;
     20 import android.content.Context;
     21 import android.content.SharedPreferences;
     22 import android.content.res.Resources;
     23 import android.os.Message;
     24 import android.os.SystemProperties;
     25 import android.preference.PreferenceManager;
     26 import android.provider.Telephony.Sms.Intents;
     27 import android.telephony.SmsCbMessage;
     28 
     29 import com.android.internal.telephony.CellBroadcastHandler;
     30 import com.android.internal.telephony.CommandsInterface;
     31 import com.android.internal.telephony.InboundSmsHandler;
     32 import com.android.internal.telephony.InboundSmsTracker;
     33 import com.android.internal.telephony.PhoneBase;
     34 import com.android.internal.telephony.SmsConstants;
     35 import com.android.internal.telephony.SmsMessageBase;
     36 import com.android.internal.telephony.SmsStorageMonitor;
     37 import com.android.internal.telephony.TelephonyProperties;
     38 import com.android.internal.telephony.WspTypeDecoder;
     39 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
     40 
     41 import java.util.Arrays;
     42 
     43 /**
     44  * Subclass of {@link InboundSmsHandler} for 3GPP2 type messages.
     45  */
     46 public class CdmaInboundSmsHandler extends InboundSmsHandler {
     47 
     48     private final CdmaSMSDispatcher mSmsDispatcher;
     49     private final CdmaServiceCategoryProgramHandler mServiceCategoryProgramHandler;
     50 
     51     private byte[] mLastDispatchedSmsFingerprint;
     52     private byte[] mLastAcknowledgedSmsFingerprint;
     53 
     54     private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
     55             com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
     56 
     57     /**
     58      * Create a new inbound SMS handler for CDMA.
     59      */
     60     private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
     61             PhoneBase phone, CdmaSMSDispatcher smsDispatcher) {
     62         super("CdmaInboundSmsHandler", context, storageMonitor, phone,
     63                 CellBroadcastHandler.makeCellBroadcastHandler(context));
     64         mSmsDispatcher = smsDispatcher;
     65         mServiceCategoryProgramHandler = CdmaServiceCategoryProgramHandler.makeScpHandler(context,
     66                 phone.mCi);
     67         phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null);
     68     }
     69 
     70     /**
     71      * Unregister for CDMA SMS.
     72      */
     73     @Override
     74     protected void onQuitting() {
     75         mPhone.mCi.unSetOnNewCdmaSms(getHandler());
     76         mCellBroadcastHandler.dispose();
     77 
     78         if (DBG) log("unregistered for 3GPP2 SMS");
     79         super.onQuitting();
     80     }
     81 
     82     /**
     83      * Wait for state machine to enter startup state. We can't send any messages until then.
     84      */
     85     public static CdmaInboundSmsHandler makeInboundSmsHandler(Context context,
     86             SmsStorageMonitor storageMonitor, PhoneBase phone, CdmaSMSDispatcher smsDispatcher) {
     87         CdmaInboundSmsHandler handler = new CdmaInboundSmsHandler(context, storageMonitor,
     88                 phone, smsDispatcher);
     89         handler.start();
     90         return handler;
     91     }
     92 
     93     /**
     94      * Return whether the device is in Emergency Call Mode (only for 3GPP2).
     95      * @return true if the device is in ECM; false otherwise
     96      */
     97     private static boolean isInEmergencyCallMode() {
     98         String inEcm = SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
     99         return "true".equals(inEcm);
    100     }
    101 
    102     /**
    103      * Return true if this handler is for 3GPP2 messages; false for 3GPP format.
    104      * @return true (3GPP2)
    105      */
    106     @Override
    107     protected boolean is3gpp2() {
    108         return true;
    109     }
    110 
    111     /**
    112      * Process Cell Broadcast, Voicemail Notification, and other 3GPP/3GPP2-specific messages.
    113      * @param smsb the SmsMessageBase object from the RIL
    114      * @return true if the message was handled here; false to continue processing
    115      */
    116     @Override
    117     protected int dispatchMessageRadioSpecific(SmsMessageBase smsb) {
    118         if (isInEmergencyCallMode()) {
    119             return Activity.RESULT_OK;
    120         }
    121 
    122         SmsMessage sms = (SmsMessage) smsb;
    123         boolean isBroadcastType = (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType());
    124 
    125         // Handle CMAS emergency broadcast messages.
    126         if (isBroadcastType) {
    127             log("Broadcast type message");
    128             SmsCbMessage cbMessage = sms.parseBroadcastSms();
    129             if (cbMessage != null) {
    130                 mCellBroadcastHandler.dispatchSmsMessage(cbMessage);
    131             } else {
    132                 loge("error trying to parse broadcast SMS");
    133             }
    134             return Intents.RESULT_SMS_HANDLED;
    135         }
    136 
    137         // Initialize fingerprint field, and see if we have a network duplicate SMS.
    138         mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
    139         if (mLastAcknowledgedSmsFingerprint != null &&
    140                 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
    141             return Intents.RESULT_SMS_HANDLED;
    142         }
    143 
    144         // Decode BD stream and set sms variables.
    145         sms.parseSms();
    146         int teleService = sms.getTeleService();
    147 
    148         switch (teleService) {
    149             case SmsEnvelope.TELESERVICE_VMN:
    150             case SmsEnvelope.TELESERVICE_MWI:
    151                 // handle voicemail indication
    152                 handleVoicemailTeleservice(sms);
    153                 return Intents.RESULT_SMS_HANDLED;
    154 
    155             case SmsEnvelope.TELESERVICE_WMT:
    156             case SmsEnvelope.TELESERVICE_WEMT:
    157                 if (sms.isStatusReportMessage()) {
    158                     mSmsDispatcher.sendStatusReportMessage(sms);
    159                     return Intents.RESULT_SMS_HANDLED;
    160                 }
    161                 break;
    162 
    163             case SmsEnvelope.TELESERVICE_SCPT:
    164                 mServiceCategoryProgramHandler.dispatchSmsMessage(sms);
    165                 return Intents.RESULT_SMS_HANDLED;
    166 
    167             case SmsEnvelope.TELESERVICE_WAP:
    168                 // handled below, after storage check
    169                 break;
    170 
    171             default:
    172                 loge("unsupported teleservice 0x" + Integer.toHexString(teleService));
    173                 return Intents.RESULT_SMS_UNSUPPORTED;
    174         }
    175 
    176         if (!mStorageMonitor.isStorageAvailable() &&
    177                 sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
    178             // It's a storable message and there's no storage available.  Bail.
    179             // (See C.S0015-B v2.0 for a description of "Immediate Display"
    180             // messages, which we represent as CLASS_0.)
    181             return Intents.RESULT_SMS_OUT_OF_MEMORY;
    182         }
    183 
    184         if (SmsEnvelope.TELESERVICE_WAP == teleService) {
    185             return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef,
    186                     sms.getOriginatingAddress(), sms.getTimestampMillis());
    187         }
    188 
    189         return dispatchNormalMessage(smsb);
    190     }
    191 
    192     /**
    193      * Send an acknowledge message.
    194      * @param success indicates that last message was successfully received.
    195      * @param result result code indicating any error
    196      * @param response callback message sent when operation completes.
    197      */
    198     @Override
    199     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
    200         if (isInEmergencyCallMode()) {
    201             return;
    202         }
    203 
    204         int causeCode = resultToCause(result);
    205         mPhone.mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
    206 
    207         if (causeCode == 0) {
    208             mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
    209         }
    210         mLastDispatchedSmsFingerprint = null;
    211     }
    212 
    213     /**
    214      * Called when the phone changes the default method updates mPhone
    215      * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject.
    216      * Override if different or other behavior is desired.
    217      *
    218      * @param phone
    219      */
    220     @Override
    221     protected void onUpdatePhoneObject(PhoneBase phone) {
    222         super.onUpdatePhoneObject(phone);
    223         mCellBroadcastHandler.updatePhoneObject(phone);
    224     }
    225 
    226     /**
    227      * Convert Android result code to CDMA SMS failure cause.
    228      * @param rc the Android SMS intent result value
    229      * @return 0 for success, or a CDMA SMS failure cause value
    230      */
    231     private static int resultToCause(int rc) {
    232         switch (rc) {
    233         case Activity.RESULT_OK:
    234         case Intents.RESULT_SMS_HANDLED:
    235             // Cause code is ignored on success.
    236             return 0;
    237         case Intents.RESULT_SMS_OUT_OF_MEMORY:
    238             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
    239         case Intents.RESULT_SMS_UNSUPPORTED:
    240             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
    241         case Intents.RESULT_SMS_GENERIC_ERROR:
    242         default:
    243             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
    244         }
    245     }
    246 
    247     /**
    248      * Handle {@link SmsEnvelope#TELESERVICE_VMN} and {@link SmsEnvelope#TELESERVICE_MWI}.
    249      * @param sms the message to process
    250      */
    251     private void handleVoicemailTeleservice(SmsMessage sms) {
    252         int voicemailCount = sms.getNumOfVoicemails();
    253         if (DBG) log("Voicemail count=" + voicemailCount);
    254 
    255         // Store the voicemail count in preferences.
    256         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
    257         SharedPreferences.Editor editor = sp.edit();
    258         editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
    259         editor.apply();
    260         mPhone.setVoiceMessageWaiting(1, voicemailCount);
    261     }
    262 
    263     /**
    264      * Processes inbound messages that are in the WAP-WDP PDU format. See
    265      * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
    266      * WDP segments are gathered until a datagram completes and gets dispatched.
    267      *
    268      * @param pdu The WAP-WDP PDU segment
    269      * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
    270      *         {@link Activity#RESULT_OK} if the message has been broadcast
    271      *         to applications
    272      */
    273     private int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address,
    274             long timestamp) {
    275         int index = 0;
    276 
    277         int msgType = (0xFF & pdu[index++]);
    278         if (msgType != 0) {
    279             log("Received a WAP SMS which is not WDP. Discard.");
    280             return Intents.RESULT_SMS_HANDLED;
    281         }
    282         int totalSegments = (0xFF & pdu[index++]);   // >= 1
    283         int segment = (0xFF & pdu[index++]);         // >= 0
    284 
    285         if (segment >= totalSegments) {
    286             loge("WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1));
    287             return Intents.RESULT_SMS_HANDLED;
    288         }
    289 
    290         // Only the first segment contains sourcePort and destination Port
    291         int sourcePort = 0;
    292         int destinationPort = 0;
    293         if (segment == 0) {
    294             //process WDP segment
    295             sourcePort = (0xFF & pdu[index++]) << 8;
    296             sourcePort |= 0xFF & pdu[index++];
    297             destinationPort = (0xFF & pdu[index++]) << 8;
    298             destinationPort |= 0xFF & pdu[index++];
    299             // Some carriers incorrectly send duplicate port fields in omadm wap pushes.
    300             // If configured, check for that here
    301             if (mCheckForDuplicatePortsInOmadmWapPush) {
    302                 if (checkDuplicatePortOmadmWapPush(pdu, index)) {
    303                     index = index + 4; // skip duplicate port fields
    304                 }
    305             }
    306         }
    307 
    308         // Lookup all other related parts
    309         log("Received WAP PDU. Type = " + msgType + ", originator = " + address
    310                 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
    311                 + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
    312 
    313         // pass the user data portion of the PDU to the shared handler in SMSDispatcher
    314         byte[] userData = new byte[pdu.length - index];
    315         System.arraycopy(pdu, index, userData, 0, pdu.length - index);
    316 
    317         InboundSmsTracker tracker = new InboundSmsTracker(userData, timestamp, destinationPort,
    318                 true, address, referenceNumber, segment, totalSegments, true);
    319 
    320         return addTrackerToRawTableAndSendMessage(tracker);
    321     }
    322 
    323     /**
    324      * Optional check to see if the received WapPush is an OMADM notification with erroneous
    325      * extra port fields.
    326      * - Some carriers make this mistake.
    327      * ex: MSGTYPE-TotalSegments-CurrentSegment
    328      *       -SourcePortDestPort-SourcePortDestPort-OMADM PDU
    329      * @param origPdu The WAP-WDP PDU segment
    330      * @param index Current Index while parsing the PDU.
    331      * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
    332      *         False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
    333      */
    334     private static boolean checkDuplicatePortOmadmWapPush(byte[] origPdu, int index) {
    335         index += 4;
    336         byte[] omaPdu = new byte[origPdu.length - index];
    337         System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
    338 
    339         WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
    340         int wspIndex = 2;
    341 
    342         // Process header length field
    343         if (!pduDecoder.decodeUintvarInteger(wspIndex)) {
    344             return false;
    345         }
    346 
    347         wspIndex += pduDecoder.getDecodedDataLength();  // advance to next field
    348 
    349         // Process content type field
    350         if (!pduDecoder.decodeContentType(wspIndex)) {
    351             return false;
    352         }
    353 
    354         String mimeType = pduDecoder.getValueString();
    355         return (WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI.equals(mimeType));
    356     }
    357 }
    358