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.Intent;
22 import android.content.IntentFilter;
23 import android.os.Message;
24 import android.os.RemoteCallback;
25 import android.os.SystemProperties;
26 import android.provider.Telephony.Sms.Intents;
27 import android.telephony.PhoneNumberUtils;
28 import android.telephony.cdma.CdmaSmsCbProgramResults;
29 
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.Phone;
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.TelephonyComponentFactory;
38 import com.android.internal.telephony.WspTypeDecoder;
39 import com.android.internal.telephony.cdma.sms.BearerData;
40 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
41 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
42 import com.android.internal.util.HexDump;
43 
44 import java.io.ByteArrayOutputStream;
45 import java.io.DataOutputStream;
46 import java.io.IOException;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 
50 /**
51  * Subclass of {@link InboundSmsHandler} for 3GPP2 type messages.
52  */
53 public class CdmaInboundSmsHandler extends InboundSmsHandler {
54 
55     private final CdmaSMSDispatcher mSmsDispatcher;
56     private static CdmaCbTestBroadcastReceiver sTestBroadcastReceiver;
57     private static CdmaScpTestBroadcastReceiver sTestScpBroadcastReceiver;
58 
59     private byte[] mLastDispatchedSmsFingerprint;
60     private byte[] mLastAcknowledgedSmsFingerprint;
61 
62     // Callback used to process the result of an SCP message
63     private RemoteCallback mScpCallback;
64 
65     private boolean mCheckForDuplicatePortsInOmadmWapPush = false;
66 
67     // When TEST_MODE is on we allow the test intent to trigger an SMS CB alert
68     private static final boolean TEST_MODE = SystemProperties.getInt("ro.debuggable", 0) == 1;
69     private static final String TEST_ACTION = "com.android.internal.telephony.cdma"
70             + ".TEST_TRIGGER_CELL_BROADCAST";
71     private static final String SCP_TEST_ACTION = "com.android.internal.telephony.cdma"
72             + ".TEST_TRIGGER_SCP_MESSAGE";
73 
74     /**
75      * Create a new inbound SMS handler for CDMA.
76      */
CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher)77     private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
78             Phone phone, CdmaSMSDispatcher smsDispatcher) {
79         super("CdmaInboundSmsHandler", context, storageMonitor, phone);
80         mSmsDispatcher = smsDispatcher;
81         mCheckForDuplicatePortsInOmadmWapPush = context.getResources().getBoolean(
82                 com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
83 
84         phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null);
85         mCellBroadcastServiceManager.enable();
86         mScpCallback = new RemoteCallback(result -> {
87             if (result == null) {
88                 loge("SCP results error: missing extras");
89                 return;
90             }
91             String sender = result.getString("sender");
92             if (sender == null) {
93                 loge("SCP results error: missing sender extra.");
94                 return;
95             }
96             ArrayList<CdmaSmsCbProgramResults> results = result.getParcelableArrayList("results");
97             if (results == null) {
98                 loge("SCP results error: missing results extra.");
99                 return;
100             }
101 
102             BearerData bData = new BearerData();
103             bData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
104             bData.messageId = SmsMessage.getNextMessageId();
105             bData.serviceCategoryProgramResults = results;
106             byte[] encodedBearerData = BearerData.encode(bData);
107 
108             ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
109             DataOutputStream dos = new DataOutputStream(baos);
110             try {
111                 dos.writeInt(SmsEnvelope.TELESERVICE_SCPT);
112                 dos.writeInt(0); //servicePresent
113                 dos.writeInt(0); //serviceCategory
114                 CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
115                         PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(sender));
116                 dos.write(destAddr.digitMode);
117                 dos.write(destAddr.numberMode);
118                 dos.write(destAddr.ton); // number_type
119                 dos.write(destAddr.numberPlan);
120                 dos.write(destAddr.numberOfDigits);
121                 dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
122                 // Subaddress is not supported.
123                 dos.write(0); //subaddressType
124                 dos.write(0); //subaddr_odd
125                 dos.write(0); //subaddr_nbr_of_digits
126                 dos.write(encodedBearerData.length);
127                 dos.write(encodedBearerData, 0, encodedBearerData.length);
128                 // Ignore the RIL response. TODO: implement retry if SMS send fails.
129                 mPhone.mCi.sendCdmaSms(baos.toByteArray(), null);
130             } catch (IOException e) {
131                 loge("exception creating SCP results PDU", e);
132             } finally {
133                 try {
134                     dos.close();
135                 } catch (IOException ignored) {
136                 }
137             }
138         });
139         if (TEST_MODE) {
140             if (sTestBroadcastReceiver == null) {
141                 sTestBroadcastReceiver = new CdmaCbTestBroadcastReceiver();
142                 IntentFilter filter = new IntentFilter();
143                 filter.addAction(TEST_ACTION);
144                 context.registerReceiver(sTestBroadcastReceiver, filter);
145             }
146             if (sTestScpBroadcastReceiver == null) {
147                 sTestScpBroadcastReceiver = new CdmaScpTestBroadcastReceiver();
148                 IntentFilter filter = new IntentFilter();
149                 filter.addAction(SCP_TEST_ACTION);
150                 context.registerReceiver(sTestScpBroadcastReceiver, filter);
151             }
152         }
153     }
154 
155     /**
156      * Unregister for CDMA SMS.
157      */
158     @Override
onQuitting()159     protected void onQuitting() {
160         mPhone.mCi.unSetOnNewCdmaSms(getHandler());
161 
162         if (DBG) log("unregistered for 3GPP2 SMS");
163         super.onQuitting();
164     }
165 
166     /**
167      * Wait for state machine to enter startup state. We can't send any messages until then.
168      */
makeInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher)169     public static CdmaInboundSmsHandler makeInboundSmsHandler(Context context,
170             SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher) {
171         CdmaInboundSmsHandler handler = new CdmaInboundSmsHandler(context, storageMonitor,
172                 phone, smsDispatcher);
173         handler.start();
174         return handler;
175     }
176 
177     /**
178      * Return true if this handler is for 3GPP2 messages; false for 3GPP format.
179      *
180      * @return true (3GPP2)
181      */
182     @Override
is3gpp2()183     protected boolean is3gpp2() {
184         return true;
185     }
186 
187     /**
188      * Process Cell Broadcast, Voicemail Notification, and other 3GPP/3GPP2-specific messages.
189      *
190      * @param smsb the SmsMessageBase object from the RIL
191      * @return true if the message was handled here; false to continue processing
192      */
193     @Override
dispatchMessageRadioSpecific(SmsMessageBase smsb)194     protected int dispatchMessageRadioSpecific(SmsMessageBase smsb) {
195         SmsMessage sms = (SmsMessage) smsb;
196         boolean isBroadcastType = (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType());
197 
198         // Handle CMAS emergency broadcast messages.
199         if (isBroadcastType) {
200             log("Broadcast type message");
201             mCellBroadcastServiceManager.sendCdmaMessageToHandler(sms);
202             return Intents.RESULT_SMS_HANDLED;
203         }
204 
205         // Initialize fingerprint field, and see if we have a network duplicate SMS.
206         mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
207         if (mLastAcknowledgedSmsFingerprint != null &&
208                 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
209             return Intents.RESULT_SMS_HANDLED;
210         }
211 
212         // Decode BD stream and set sms variables.
213         sms.parseSms();
214         int teleService = sms.getTeleService();
215 
216         switch (teleService) {
217             case SmsEnvelope.TELESERVICE_VMN:
218             case SmsEnvelope.TELESERVICE_MWI:
219                 // handle voicemail indication
220                 handleVoicemailTeleservice(sms);
221                 return Intents.RESULT_SMS_HANDLED;
222 
223             case SmsEnvelope.TELESERVICE_WMT:
224             case SmsEnvelope.TELESERVICE_WEMT:
225                 if (sms.isStatusReportMessage()) {
226                     mSmsDispatcher.sendStatusReportMessage(sms);
227                     return Intents.RESULT_SMS_HANDLED;
228                 }
229                 break;
230 
231             case SmsEnvelope.TELESERVICE_SCPT:
232                 mCellBroadcastServiceManager.sendCdmaScpMessageToHandler(sms, mScpCallback);
233                 return Intents.RESULT_SMS_HANDLED;
234 
235             case SmsEnvelope.TELESERVICE_FDEA_WAP:
236                 if (!sms.preprocessCdmaFdeaWap()) {
237                     return Intents.RESULT_SMS_HANDLED;
238                 }
239                 teleService = SmsEnvelope.TELESERVICE_WAP;
240                 // fall through
241             case SmsEnvelope.TELESERVICE_WAP:
242                 // handled below, after storage check
243                 break;
244 
245             default:
246                 loge("unsupported teleservice 0x" + Integer.toHexString(teleService));
247                 return Intents.RESULT_SMS_UNSUPPORTED;
248         }
249 
250         if (!mStorageMonitor.isStorageAvailable() &&
251                 sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
252             // It's a storable message and there's no storage available.  Bail.
253             // (See C.S0015-B v2.0 for a description of "Immediate Display"
254             // messages, which we represent as CLASS_0.)
255             return Intents.RESULT_SMS_OUT_OF_MEMORY;
256         }
257 
258         if (SmsEnvelope.TELESERVICE_WAP == teleService) {
259             return processCdmaWapPdu(sms.getUserData(), sms.mMessageRef,
260                     sms.getOriginatingAddress(), sms.getDisplayOriginatingAddress(),
261                     sms.getTimestampMillis());
262         }
263 
264         return dispatchNormalMessage(smsb);
265     }
266 
267     /**
268      * Send an acknowledge message.
269      *
270      * @param success  indicates that last message was successfully received.
271      * @param result   result code indicating any error
272      * @param response callback message sent when operation completes.
273      */
274     @Override
acknowledgeLastIncomingSms(boolean success, int result, Message response)275     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
276         int causeCode = resultToCause(result);
277         mPhone.mCi.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
278 
279         if (causeCode == 0) {
280             mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
281         }
282         mLastDispatchedSmsFingerprint = null;
283     }
284 
285     /**
286      * Convert Android result code to CDMA SMS failure cause.
287      *
288      * @param rc the Android SMS intent result value
289      * @return 0 for success, or a CDMA SMS failure cause value
290      */
resultToCause(int rc)291     private static int resultToCause(int rc) {
292         switch (rc) {
293             case Activity.RESULT_OK:
294             case Intents.RESULT_SMS_HANDLED:
295                 // Cause code is ignored on success.
296                 return 0;
297             case Intents.RESULT_SMS_OUT_OF_MEMORY:
298                 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
299             case Intents.RESULT_SMS_UNSUPPORTED:
300                 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
301             case Intents.RESULT_SMS_GENERIC_ERROR:
302             default:
303                 return CommandsInterface.CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM;
304         }
305     }
306 
307     /**
308      * Handle {@link SmsEnvelope#TELESERVICE_VMN} and {@link SmsEnvelope#TELESERVICE_MWI}.
309      *
310      * @param sms the message to process
311      */
handleVoicemailTeleservice(SmsMessage sms)312     private void handleVoicemailTeleservice(SmsMessage sms) {
313         int voicemailCount = sms.getNumOfVoicemails();
314         if (DBG) log("Voicemail count=" + voicemailCount);
315 
316         // range check
317         if (voicemailCount < 0) {
318             voicemailCount = -1;
319         } else if (voicemailCount > 99) {
320             // C.S0015-B v2, 4.5.12
321             // range: 0-99
322             voicemailCount = 99;
323         }
324         // update voice mail count in phone
325         mPhone.setVoiceMessageCount(voicemailCount);
326         // update metrics
327         addVoicemailSmsToMetrics();
328     }
329 
330     /**
331      * Processes inbound messages that are in the WAP-WDP PDU format. See
332      * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
333      * WDP segments are gathered until a datagram completes and gets dispatched.
334      *
335      * @param pdu The WAP-WDP PDU segment
336      * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
337      * {@link Activity#RESULT_OK} if the message has been broadcast
338      * to applications
339      */
processCdmaWapPdu(byte[] pdu, int referenceNumber, String address, String dispAddr, long timestamp)340     private int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address, String dispAddr,
341             long timestamp) {
342         int index = 0;
343 
344         int msgType = (0xFF & pdu[index++]);
345         if (msgType != 0) {
346             log("Received a WAP SMS which is not WDP. Discard.");
347             return Intents.RESULT_SMS_HANDLED;
348         }
349         int totalSegments = (0xFF & pdu[index++]);   // >= 1
350         int segment = (0xFF & pdu[index++]);         // >= 0
351 
352         if (segment >= totalSegments) {
353             loge("WDP bad segment #" + segment + " expecting 0-" + (totalSegments - 1));
354             return Intents.RESULT_SMS_HANDLED;
355         }
356 
357         // Only the first segment contains sourcePort and destination Port
358         int sourcePort = 0;
359         int destinationPort = 0;
360         if (segment == 0) {
361             //process WDP segment
362             sourcePort = (0xFF & pdu[index++]) << 8;
363             sourcePort |= 0xFF & pdu[index++];
364             destinationPort = (0xFF & pdu[index++]) << 8;
365             destinationPort |= 0xFF & pdu[index++];
366             // Some carriers incorrectly send duplicate port fields in omadm wap pushes.
367             // If configured, check for that here
368             if (mCheckForDuplicatePortsInOmadmWapPush) {
369                 if (checkDuplicatePortOmadmWapPush(pdu, index)) {
370                     index = index + 4; // skip duplicate port fields
371                 }
372             }
373         }
374 
375         // Lookup all other related parts
376         log("Received WAP PDU. Type = " + msgType + ", originator = " + address
377                 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
378                 + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
379 
380         // pass the user data portion of the PDU to the shared handler in SMSDispatcher
381         byte[] userData = new byte[pdu.length - index];
382         System.arraycopy(pdu, index, userData, 0, pdu.length - index);
383         InboundSmsTracker tracker = TelephonyComponentFactory.getInstance()
384                 .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(
385                         userData, timestamp, destinationPort, true, address, dispAddr,
386                         referenceNumber,
387                         segment, totalSegments, true, HexDump.toHexString(userData),
388                         false /* isClass0 */,
389                         mPhone.getSubId());
390 
391         // de-duping is done only for text messages
392         return addTrackerToRawTableAndSendMessage(tracker, false /* don't de-dup */);
393     }
394 
395     /**
396      * Optional check to see if the received WapPush is an OMADM notification with erroneous
397      * extra port fields.
398      * - Some carriers make this mistake.
399      * ex: MSGTYPE-TotalSegments-CurrentSegment
400      * -SourcePortDestPort-SourcePortDestPort-OMADM PDU
401      *
402      * @param origPdu The WAP-WDP PDU segment
403      * @param index   Current Index while parsing the PDU.
404      * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
405      * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
406      */
checkDuplicatePortOmadmWapPush(byte[] origPdu, int index)407     private static boolean checkDuplicatePortOmadmWapPush(byte[] origPdu, int index) {
408         index += 4;
409         byte[] omaPdu = new byte[origPdu.length - index];
410         System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
411 
412         WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
413         int wspIndex = 2;
414 
415         // Process header length field
416         if (!pduDecoder.decodeUintvarInteger(wspIndex)) {
417             return false;
418         }
419 
420         wspIndex += pduDecoder.getDecodedDataLength();  // advance to next field
421 
422         // Process content type field
423         if (!pduDecoder.decodeContentType(wspIndex)) {
424             return false;
425         }
426 
427         String mimeType = pduDecoder.getValueString();
428         return (WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI.equals(mimeType));
429     }
430 
431     /**
432      * Add voicemail indication SMS 0 to metrics.
433      */
addVoicemailSmsToMetrics()434     private void addVoicemailSmsToMetrics() {
435         mMetrics.writeIncomingVoiceMailSms(mPhone.getPhoneId(),
436                 android.telephony.SmsMessage.FORMAT_3GPP2);
437     }
438 
439     /**
440      * A broadcast receiver used for testing emergency cell broadcasts. To trigger test CDMA cell
441      * broadcasts with adb run e.g:
442      *
443      * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_CELL_BROADCAST \
444      * --ei service_category 4097 \
445      * --es bearer_data_string 00031303900801C00D0101015C02D00002BFD1931054D208119313D3D10815D05 \
446      * 493925391C81193D48814D3D555120810D3D0D3D3925393C810D3D5539516480B481393D495120810D1539514 \
447      * 9053081054925693D390481553951253080D0C4D481413481354D500
448      *
449      * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_CELL_BROADCAST \
450      * --ei service_category 4097 \
451      * --es bearer_data_string 00031303900801C00D0101015C02D00002BFD1931054D208119313D3D10815D05 \
452      * 493925391C81193D48814D3D555120810D3D0D3D3925393C810D3D5539516480B481393D495120810D1539514 \
453      * 9053081054925693D390481553951253080D0C4D481413481354D500 \
454      * --ei phone_id 0 \
455      */
456     private class CdmaCbTestBroadcastReceiver extends CbTestBroadcastReceiver {
457 
CdmaCbTestBroadcastReceiver()458         CdmaCbTestBroadcastReceiver() {
459             super(TEST_ACTION);
460         }
461 
462         @Override
handleTestAction(Intent intent)463         protected void handleTestAction(Intent intent) {
464             SmsEnvelope envelope = new SmsEnvelope();
465             // the CdmaSmsAddress is not used for a test cell broadcast message, but needs to be
466             // supplied to avoid a null pointer exception in the platform
467             CdmaSmsAddress nonNullAddress = new CdmaSmsAddress();
468             nonNullAddress.origBytes = new byte[]{(byte) 0xFF};
469             envelope.origAddress = nonNullAddress;
470 
471             // parse service category from intent
472             envelope.serviceCategory = intent.getIntExtra("service_category", -1);
473             if (envelope.serviceCategory == -1) {
474                 log("No service category, ignoring CB test intent");
475                 return;
476             }
477 
478             // parse bearer data from intent
479             String bearerDataString = intent.getStringExtra("bearer_data_string");
480             envelope.bearerData = decodeHexString(bearerDataString);
481             if (envelope.bearerData == null) {
482                 log("No bearer data, ignoring CB test intent");
483                 return;
484             }
485 
486             SmsMessage sms = new SmsMessage(new CdmaSmsAddress(), envelope);
487                 mCellBroadcastServiceManager.sendCdmaMessageToHandler(sms);
488         }
489     }
490 
491     /**
492      * A broadcast receiver used for testing CDMA SCP messages. To trigger test CDMA SCP messages
493      * with adb run e.g:
494      *
495      * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_SCP_MESSAGE \
496      * --es originating_address_string 1234567890 \
497      * --es bearer_data_string 00031007B0122610880080B2091C5F1D3965DB95054D1CB2E1E883A6F41334E \
498      * 6CA830EEC882872DFC32F2E9E40
499      */
500     private class CdmaScpTestBroadcastReceiver extends CbTestBroadcastReceiver {
501 
CdmaScpTestBroadcastReceiver()502         CdmaScpTestBroadcastReceiver() {
503             super(SCP_TEST_ACTION);
504         }
505 
506         @Override
handleTestAction(Intent intent)507         protected void handleTestAction(Intent intent) {
508             SmsEnvelope envelope = new SmsEnvelope();
509             // the CdmaSmsAddress is not used for a test SCP message, but needs to be supplied to
510             // avoid a null pointer exception in the platform
511             CdmaSmsAddress nonNullAddress = new CdmaSmsAddress();
512             nonNullAddress.origBytes = new byte[]{(byte) 0xFF};
513             envelope.origAddress = nonNullAddress;
514 
515             // parse bearer data from intent
516             String bearerDataString = intent.getStringExtra("bearer_data_string");
517             envelope.bearerData = decodeHexString(bearerDataString);
518             if (envelope.bearerData == null) {
519                 log("No bearer data, ignoring SCP test intent");
520                 return;
521             }
522 
523             CdmaSmsAddress origAddr = new CdmaSmsAddress();
524             String addressString = intent.getStringExtra("originating_address_string");
525             origAddr.origBytes = decodeHexString(addressString);
526             if (origAddr.origBytes == null) {
527                 log("No address data, ignoring SCP test intent");
528                 return;
529             }
530             SmsMessage sms = new SmsMessage(origAddr, envelope);
531             sms.parseSms();
532             mCellBroadcastServiceManager.sendCdmaScpMessageToHandler(sms, mScpCallback);
533         }
534     }
535 }
536