Revert "Telephony: Voice mail notification related changes"
[android/platform/frameworks/opt/telephony.git] / src / java / com / android / internal / telephony / cdma / CdmaInboundSmsHandler.java
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, phone));
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 + mPhone.getPhoneId(), 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 }