OSDN Git Service

am 0fbe1dfb: Merge "cherrypick from master: Change-Id: I169749dc594ca1d79a802db4c53ec...
[android-x86/frameworks-base.git] / telephony / java / com / android / internal / telephony / cdma / CdmaSMSDispatcher.java
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.PendingIntent;
22 import android.app.PendingIntent.CanceledException;
23 import android.content.ContentValues;
24 import android.content.Intent;
25 import android.content.SharedPreferences;
26 import android.database.Cursor;
27 import android.database.SQLException;
28 import android.os.AsyncResult;
29 import android.os.Message;
30 import android.os.SystemProperties;
31 import android.preference.PreferenceManager;
32 import android.provider.Telephony;
33 import android.provider.Telephony.Sms.Intents;
34 import android.telephony.SmsManager;
35 import android.telephony.SmsMessage.MessageClass;
36 import android.util.Config;
37 import android.util.Log;
38
39 import com.android.internal.telephony.CommandsInterface;
40 import com.android.internal.telephony.SMSDispatcher;
41 import com.android.internal.telephony.SmsHeader;
42 import com.android.internal.telephony.SmsMessageBase;
43 import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
44 import com.android.internal.telephony.TelephonyProperties;
45 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
46 import com.android.internal.telephony.cdma.sms.UserData;
47 import com.android.internal.util.HexDump;
48
49 import java.io.ByteArrayOutputStream;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.HashMap;
53
54
55 final class CdmaSMSDispatcher extends SMSDispatcher {
56     private static final String TAG = "CDMA";
57
58     private byte[] mLastDispatchedSmsFingerprint;
59     private byte[] mLastAcknowledgedSmsFingerprint;
60
61     CdmaSMSDispatcher(CDMAPhone phone) {
62         super(phone);
63     }
64
65     /**
66      * Called when a status report is received.  This should correspond to
67      * a previously successful SEND.
68      * Is a special GSM function, should never be called in CDMA!!
69      *
70      * @param ar AsyncResult passed into the message handler.  ar.result should
71      *           be a String representing the status report PDU, as ASCII hex.
72      */
73     @Override
74     protected void handleStatusReport(AsyncResult ar) {
75         Log.d(TAG, "handleStatusReport is a special GSM function, should never be called in CDMA!");
76     }
77
78     private void handleCdmaStatusReport(SmsMessage sms) {
79         for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
80             SmsTracker tracker = deliveryPendingList.get(i);
81             if (tracker.mMessageRef == sms.messageRef) {
82                 // Found it.  Remove from list and broadcast.
83                 deliveryPendingList.remove(i);
84                 PendingIntent intent = tracker.mDeliveryIntent;
85                 Intent fillIn = new Intent();
86                 fillIn.putExtra("pdu", sms.getPdu());
87                 try {
88                     intent.send(mContext, Activity.RESULT_OK, fillIn);
89                 } catch (CanceledException ex) {}
90                 break;  // Only expect to see one tracker matching this message.
91             }
92         }
93     }
94
95     /** {@inheritDoc} */
96     @Override
97     public int dispatchMessage(SmsMessageBase smsb) {
98
99         // If sms is null, means there was a parsing error.
100         if (smsb == null) {
101             Log.e(TAG, "dispatchMessage: message is null");
102             return Intents.RESULT_SMS_GENERIC_ERROR;
103         }
104
105         String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
106         if (inEcm.equals("true")) {
107             return Activity.RESULT_OK;
108         }
109
110         if (mSmsReceiveDisabled) {
111             // Device doesn't support receiving SMS,
112             Log.d(TAG, "Received short message on device which doesn't support "
113                     + "receiving SMS. Ignored.");
114             return Intents.RESULT_SMS_HANDLED;
115         }
116
117         // See if we have a network duplicate SMS.
118         SmsMessage sms = (SmsMessage) smsb;
119         mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
120         if (mLastAcknowledgedSmsFingerprint != null &&
121                 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
122             return Intents.RESULT_SMS_HANDLED;
123         }
124         // Decode BD stream and set sms variables.
125         sms.parseSms();
126         int teleService = sms.getTeleService();
127         boolean handled = false;
128
129         if ((SmsEnvelope.TELESERVICE_VMN == teleService) ||
130                 (SmsEnvelope.TELESERVICE_MWI == teleService)) {
131             // handling Voicemail
132             int voicemailCount = sms.getNumOfVoicemails();
133             Log.d(TAG, "Voicemail count=" + voicemailCount);
134             // Store the voicemail count in preferences.
135             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
136                     mPhone.getContext());
137             SharedPreferences.Editor editor = sp.edit();
138             editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
139             editor.apply();
140             ((CDMAPhone) mPhone).updateMessageWaitingIndicator(voicemailCount);
141             handled = true;
142         } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
143                 (SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
144                 sms.isStatusReportMessage()) {
145             handleCdmaStatusReport(sms);
146             handled = true;
147         } else if ((sms.getUserData() == null)) {
148             if (Config.LOGD) {
149                 Log.d(TAG, "Received SMS without user data");
150             }
151             handled = true;
152         }
153
154         if (handled) {
155             return Intents.RESULT_SMS_HANDLED;
156         }
157
158         if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) {
159             // It's a storable message and there's no storage available.  Bail.
160             // (See C.S0015-B v2.0 for a description of "Immediate Display"
161             // messages, which we represent as CLASS_0.)
162             return Intents.RESULT_SMS_OUT_OF_MEMORY;
163         }
164
165         if (SmsEnvelope.TELESERVICE_WAP == teleService) {
166             return processCdmaWapPdu(sms.getUserData(), sms.messageRef,
167                     sms.getOriginatingAddress());
168         }
169
170         // Reject (NAK) any messages with teleservice ids that have
171         // not yet been handled and also do not correspond to the two
172         // kinds that are processed below.
173         if ((SmsEnvelope.TELESERVICE_WMT != teleService) &&
174                 (SmsEnvelope.TELESERVICE_WEMT != teleService) &&
175                 (SmsEnvelope.MESSAGE_TYPE_BROADCAST != sms.getMessageType())) {
176             return Intents.RESULT_SMS_UNSUPPORTED;
177         }
178
179         /*
180          * TODO(cleanup): Why are we using a getter method for this
181          * (and for so many other sms fields)?  Trivial getters and
182          * setters like this are direct violations of the style guide.
183          * If the purpose is to protect against writes (by not
184          * providing a setter) then any protection is illusory (and
185          * hence bad) for cases where the values are not primitives,
186          * such as this call for the header.  Since this is an issue
187          * with the public API it cannot be changed easily, but maybe
188          * something can be done eventually.
189          */
190         SmsHeader smsHeader = sms.getUserDataHeader();
191
192         /*
193          * TODO(cleanup): Since both CDMA and GSM use the same header
194          * format, this dispatch processing is naturally identical,
195          * and code should probably not be replicated explicitly.
196          */
197
198         // See if message is partial or port addressed.
199         if ((smsHeader == null) || (smsHeader.concatRef == null)) {
200             // Message is not partial (not part of concatenated sequence).
201             byte[][] pdus = new byte[1][];
202             pdus[0] = sms.getPdu();
203
204             if (smsHeader != null && smsHeader.portAddrs != null) {
205                 if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
206                     // GSM-style WAP indication
207                     return mWapPush.dispatchWapPdu(sms.getUserData());
208                 } else {
209                     // The message was sent to a port, so concoct a URI for it.
210                     dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
211                 }
212             } else {
213                 // Normal short and non-port-addressed message, dispatch it.
214                 dispatchPdus(pdus);
215             }
216             return Activity.RESULT_OK;
217         } else {
218             // Process the message part.
219             return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
220         }
221     }
222
223     /**
224      * Processes inbound messages that are in the WAP-WDP PDU format. See
225      * wap-259-wdp-20010614-a section 6.5 for details on the WAP-WDP PDU format.
226      * WDP segments are gathered until a datagram completes and gets dispatched.
227      *
228      * @param pdu The WAP-WDP PDU segment
229      * @return a result code from {@link Telephony.Sms.Intents}, or
230      *         {@link Activity#RESULT_OK} if the message has been broadcast
231      *         to applications
232      */
233     protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
234         int segment;
235         int totalSegments;
236         int index = 0;
237         int msgType;
238
239         int sourcePort = 0;
240         int destinationPort = 0;
241
242         msgType = pdu[index++];
243         if (msgType != 0){
244             Log.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
245             return Intents.RESULT_SMS_HANDLED;
246         }
247         totalSegments = pdu[index++]; // >=1
248         segment = pdu[index++]; // >=0
249
250         // Only the first segment contains sourcePort and destination Port
251         if (segment == 0) {
252             //process WDP segment
253             sourcePort = (0xFF & pdu[index++]) << 8;
254             sourcePort |= 0xFF & pdu[index++];
255             destinationPort = (0xFF & pdu[index++]) << 8;
256             destinationPort |= 0xFF & pdu[index++];
257         }
258
259         // Lookup all other related parts
260         StringBuilder where = new StringBuilder("reference_number =");
261         where.append(referenceNumber);
262         where.append(" AND address = ?");
263         String[] whereArgs = new String[] {address};
264
265         Log.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
266                 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
267                 + ", ID = " + referenceNumber + ", segment# = " + segment + "/" + totalSegments);
268
269         byte[][] pdus = null;
270         Cursor cursor = null;
271         try {
272             cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
273             int cursorCount = cursor.getCount();
274             if (cursorCount != totalSegments - 1) {
275                 // We don't have all the parts yet, store this one away
276                 ContentValues values = new ContentValues();
277                 values.put("date", new Long(0));
278                 values.put("pdu", HexDump.toHexString(pdu, index, pdu.length - index));
279                 values.put("address", address);
280                 values.put("reference_number", referenceNumber);
281                 values.put("count", totalSegments);
282                 values.put("sequence", segment);
283                 values.put("destination_port", destinationPort);
284
285                 mResolver.insert(mRawUri, values);
286
287                 return Intents.RESULT_SMS_HANDLED;
288             }
289
290             // All the parts are in place, deal with them
291             int pduColumn = cursor.getColumnIndex("pdu");
292             int sequenceColumn = cursor.getColumnIndex("sequence");
293
294             pdus = new byte[totalSegments][];
295             for (int i = 0; i < cursorCount; i++) {
296                 cursor.moveToNext();
297                 int cursorSequence = (int)cursor.getLong(sequenceColumn);
298                 // Read the destination port from the first segment
299                 if (cursorSequence == 0) {
300                     int destinationPortColumn = cursor.getColumnIndex("destination_port");
301                     destinationPort = (int)cursor.getLong(destinationPortColumn);
302                 }
303                 pdus[cursorSequence] = HexDump.hexStringToByteArray(
304                         cursor.getString(pduColumn));
305             }
306             // The last part will be added later
307
308             // Remove the parts from the database
309             mResolver.delete(mRawUri, where.toString(), whereArgs);
310         } catch (SQLException e) {
311             Log.e(TAG, "Can't access multipart SMS database", e);
312             return Intents.RESULT_SMS_GENERIC_ERROR;
313         } finally {
314             if (cursor != null) cursor.close();
315         }
316
317         // Build up the data stream
318         ByteArrayOutputStream output = new ByteArrayOutputStream();
319         for (int i = 0; i < totalSegments; i++) {
320             // reassemble the (WSP-)pdu
321             if (i == segment) {
322                 // This one isn't in the DB, so add it
323                 output.write(pdu, index, pdu.length - index);
324             } else {
325                 output.write(pdus[i], 0, pdus[i].length);
326             }
327         }
328
329         byte[] datagram = output.toByteArray();
330         // Dispatch the PDU to applications
331         switch (destinationPort) {
332         case SmsHeader.PORT_WAP_PUSH:
333             // Handle the PUSH
334             return mWapPush.dispatchWapPdu(datagram);
335
336         default:{
337             pdus = new byte[1][];
338             pdus[0] = datagram;
339             // The messages were sent to any other WAP port
340             dispatchPortAddressedPdus(pdus, destinationPort);
341             return Activity.RESULT_OK;
342         }
343         }
344     }
345
346     /** {@inheritDoc} */
347     @Override
348     protected void sendData(String destAddr, String scAddr, int destPort,
349             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
350         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
351                 scAddr, destAddr, destPort, data, (deliveryIntent != null));
352         sendSubmitPdu(pdu, sentIntent, deliveryIntent);
353     }
354
355     /** {@inheritDoc} */
356     @Override
357     protected void sendText(String destAddr, String scAddr, String text,
358             PendingIntent sentIntent, PendingIntent deliveryIntent) {
359         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
360                 scAddr, destAddr, text, (deliveryIntent != null), null);
361         sendSubmitPdu(pdu, sentIntent, deliveryIntent);
362     }
363
364     /** {@inheritDoc} */
365     @Override
366     protected void sendMultipartText(String destAddr, String scAddr,
367             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
368             ArrayList<PendingIntent> deliveryIntents) {
369
370         /**
371          * TODO(cleanup): There is no real code difference between
372          * this and the GSM version, and hence it should be moved to
373          * the base class or consolidated somehow, provided calling
374          * the proper submit pdu stuff can be arranged.
375          */
376
377         int refNumber = getNextConcatenatedRef() & 0x00FF;
378         int msgCount = parts.size();
379         int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
380
381         for (int i = 0; i < msgCount; i++) {
382             TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
383             if (encoding != details.codeUnitSize
384                     && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
385                             || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
386                 encoding = details.codeUnitSize;
387             }
388         }
389
390         for (int i = 0; i < msgCount; i++) {
391             SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
392             concatRef.refNumber = refNumber;
393             concatRef.seqNumber = i + 1;  // 1-based sequence
394             concatRef.msgCount = msgCount;
395             concatRef.isEightBits = true;
396             SmsHeader smsHeader = new SmsHeader();
397             smsHeader.concatRef = concatRef;
398
399             PendingIntent sentIntent = null;
400             if (sentIntents != null && sentIntents.size() > i) {
401                 sentIntent = sentIntents.get(i);
402             }
403
404             PendingIntent deliveryIntent = null;
405             if (deliveryIntents != null && deliveryIntents.size() > i) {
406                 deliveryIntent = deliveryIntents.get(i);
407             }
408
409             UserData uData = new UserData();
410             uData.payloadStr = parts.get(i);
411             uData.userDataHeader = smsHeader;
412             if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
413                 uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
414             } else { // assume UTF-16
415                 uData.msgEncoding = UserData.ENCODING_UNICODE_16;
416             }
417             uData.msgEncodingSet = true;
418
419             /* By setting the statusReportRequested bit only for the
420              * last message fragment, this will result in only one
421              * callback to the sender when that last fragment delivery
422              * has been acknowledged. */
423             SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destAddr,
424                     uData, (deliveryIntent != null) && (i == (msgCount - 1)));
425
426             sendSubmitPdu(submitPdu, sentIntent, deliveryIntent);
427         }
428     }
429
430     protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
431             PendingIntent sentIntent, PendingIntent deliveryIntent) {
432         if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
433             if (sentIntent != null) {
434                 try {
435                     sentIntent.send(SmsManager.RESULT_ERROR_NO_SERVICE);
436                 } catch (CanceledException ex) {}
437             }
438             if (Config.LOGD) {
439                 Log.d(TAG, "Block SMS in Emergency Callback mode");
440             }
441             return;
442         }
443         sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
444     }
445
446     /** {@inheritDoc} */
447     @Override
448     protected void sendSms(SmsTracker tracker) {
449         HashMap<String, Object> map = tracker.mData;
450
451         // byte smsc[] = (byte[]) map.get("smsc");  // unused for CDMA
452         byte pdu[] = (byte[]) map.get("pdu");
453
454         Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
455
456         mCm.sendCdmaSms(pdu, reply);
457     }
458
459      /** {@inheritDoc} */
460     @Override
461     protected void sendMultipartSms (SmsTracker tracker) {
462         Log.d(TAG, "TODO: CdmaSMSDispatcher.sendMultipartSms not implemented");
463     }
464
465     /** {@inheritDoc} */
466     @Override
467     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
468         // FIXME unit test leaves cm == null. this should change
469
470         String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
471         if (inEcm.equals("true")) {
472             return;
473         }
474
475         if (mCm != null) {
476             int causeCode = resultToCause(result);
477             mCm.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
478
479             if (causeCode == 0) {
480                 mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
481             }
482             mLastDispatchedSmsFingerprint = null;
483         }
484     }
485
486     /** {@inheritDoc} */
487     @Override
488     public void activateCellBroadcastSms(int activate, Message response) {
489         mCm.setCdmaBroadcastActivation((activate == 0), response);
490     }
491
492     /** {@inheritDoc} */
493     @Override
494     public void getCellBroadcastSmsConfig(Message response) {
495         mCm.getCdmaBroadcastConfig(response);
496     }
497
498     /** {@inheritDoc} */
499     @Override
500     public void setCellBroadcastConfig(int[] configValuesArray, Message response) {
501         mCm.setCdmaBroadcastConfig(configValuesArray, response);
502     }
503
504     protected void handleBroadcastSms(AsyncResult ar) {
505         // Not supported
506         Log.e(TAG, "Error! Not implemented for CDMA.");
507     }
508
509     private int resultToCause(int rc) {
510         switch (rc) {
511         case Activity.RESULT_OK:
512         case Intents.RESULT_SMS_HANDLED:
513             // Cause code is ignored on success.
514             return 0;
515         case Intents.RESULT_SMS_OUT_OF_MEMORY:
516             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
517         case Intents.RESULT_SMS_UNSUPPORTED:
518             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
519         case Intents.RESULT_SMS_GENERIC_ERROR:
520         default:
521             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
522         }
523     }
524 }