From aeead2151e43439842b0efd3765f6780b592a49d Mon Sep 17 00:00:00 2001 From: yanglv Date: Mon, 26 Jan 2015 16:29:24 +0800 Subject: TelephonyProvider: Implement inserting SMS into ICC card by provider There will be an exception when inserting SMS into CDMA card due to new SELinux policy on L version, it can't set "radio" system property with a non-radio user ID. Move inserting SMS into ICC card implementation from Mms application to TelephonyProvider which is phone process. Change-Id: I187d2d10ff7fc73976ef699c5df0995ca29ec2bd CRs-Fixed: 775189 --- .../android/providers/telephony/SmsProvider.java | 365 +++++++++++++++++++++ 1 file changed, 365 insertions(+) mode change 100644 => 100755 src/com/android/providers/telephony/SmsProvider.java diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java old mode 100644 new mode 100755 index 19a1eb7..736e86a --- a/src/com/android/providers/telephony/SmsProvider.java +++ b/src/com/android/providers/telephony/SmsProvider.java @@ -16,6 +16,12 @@ package com.android.providers.telephony; +import static android.telephony.SmsMessage.ENCODING_16BIT; +import static android.telephony.SmsMessage.ENCODING_7BIT; +import static android.telephony.SmsMessage.ENCODING_UNKNOWN; +import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; +import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS; + import android.app.AppOpsManager; import android.content.ContentProvider; import android.content.ContentResolver; @@ -38,14 +44,30 @@ import android.provider.Telephony.MmsSms; import android.provider.Telephony.Sms; import android.provider.Telephony.TextBasedSmsColumns; import android.provider.Telephony.Threads; +import android.telephony.PhoneNumberUtils; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; +import java.util.Locale; + +import com.android.internal.telephony.EncodeException; +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.SmsHeader; +import com.android.internal.telephony.cdma.sms.BearerData; +import com.android.internal.telephony.cdma.sms.CdmaSmsAddress; +import com.android.internal.telephony.cdma.sms.UserData; public class SmsProvider extends ContentProvider { private static final Uri NOTIFICATION_URI = Uri.parse("content://sms"); @@ -61,11 +83,19 @@ public class SmsProvider extends ContentProvider { private static final int SUB1 = 0; private static final int SUB2 = 1; private static final Integer ONE = Integer.valueOf(1); + private static final int OFFSET_ADDRESS_LENGTH = 0; + private static final int OFFSET_TOA = 1; + private static final int OFFSET_ADDRESS_VALUE = 2; + private static final int TIMESTAMP_LENGTH = 7; // See TS 23.040 9.2.3.11 private static final String[] CONTACT_QUERY_PROJECTION = new String[] { Contacts.Phones.PERSON_ID }; private static final int PERSON_ID_COLUMN = 0; + private static final String SMS_BOX_ID = "box_id"; + private static final Uri INSERT_SMS_INTO_ICC_SUCCESS = Uri.parse("content://iccsms/success"); + private static final Uri INSERT_SMS_INTO_ICC_FAIL = Uri.parse("content://iccsms/fail"); + /** * These are the columns that are available when reading SMS * messages from the ICC. Columns whose names begin with "is_" @@ -474,6 +504,9 @@ public class SmsProvider extends ContentProvider { table = "canonical_addresses"; break; + case SMS_ALL_ICC: + return insertMessageIntoIcc(initialValues); + default: Log.e(TAG, "Invalid request: " + url); return null; @@ -599,6 +632,338 @@ public class SmsProvider extends ContentProvider { return null; } + private Uri insertMessageIntoIcc(ContentValues values) { + if (values == null) { + return INSERT_SMS_INTO_ICC_FAIL; + } + long subId = values.getAsLong(PhoneConstants.SUBSCRIPTION_KEY); + String address = values.getAsString(Sms.ADDRESS); + String message = values.getAsString(Sms.BODY); + int boxId = values.getAsInteger(SMS_BOX_ID); + long timestamp = values.getAsLong(Sms.DATE); + byte pdu[] = null; + int status; + if (Sms.isOutgoingFolder(boxId)) { + pdu = SmsMessage.getSubmitPdu(null, address, message, false, subId).encodedMessage; + status = SmsManager.STATUS_ON_ICC_SENT; + } else { + pdu = getDeliveryPdu(null, address, message, timestamp, subId); + status = SmsManager.STATUS_ON_ICC_READ; + } + boolean result = SmsManager.getSmsManagerForSubscriber(subId).copyMessageToIcc(null, + pdu, status); + return result ? INSERT_SMS_INTO_ICC_SUCCESS : INSERT_SMS_INTO_ICC_FAIL; + } + + /** + * Generate a Delivery PDU byte array. see getSubmitPdu for reference. + */ + public static byte[] getDeliveryPdu(String scAddress, String destinationAddress, String message, + long date, long subscription) { + if (isCdmaPhone(subscription)) { + return getCdmaDeliveryPdu(scAddress, destinationAddress, message, date); + } else { + return getGsmDeliveryPdu(scAddress, destinationAddress, message, date, null, + ENCODING_UNKNOWN); + } + } + + private static boolean isCdmaPhone(long subscription) { + boolean isCdma = false; + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subscription); + if (TelephonyManager.PHONE_TYPE_CDMA == activePhone) { + isCdma = true; + } + return isCdma; + } + + public static byte[] getCdmaDeliveryPdu(String scAddress, String destinationAddress, + String message, long date) { + // Perform null parameter checks. + if (message == null || destinationAddress == null) { + Log.d(TAG, "getCDMADeliveryPdu,message =null"); + return null; + } + + // according to submit pdu encoding as written in privateGetSubmitPdu + + // MTI = SMS-DELIVERY, UDHI = header != null + byte[] header = null; + byte mtiByte = (byte) (0x00 | (header != null ? 0x40 : 0x00)); + ByteArrayOutputStream headerStream = getDeliveryPduHeader(destinationAddress, mtiByte); + + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(MAX_USER_DATA_BYTES + 40); + + DataOutputStream dos = new DataOutputStream(byteStream); + // int status,Status of message. See TS 27.005 3.1, "" + + /* 0 = "REC UNREAD" */ + /* 1 = "REC READ" */ + /* 2 = "STO UNSENT" */ + /* 3 = "STO SENT" */ + + try { + // int uTeleserviceID; + int uTeleserviceID = 0; //.TELESERVICE_CT_WAP;// int + dos.writeInt(uTeleserviceID); + + // unsigned char bIsServicePresent + byte bIsServicePresent = 0;// byte + dos.writeInt(bIsServicePresent); + + // uServicecategory + int uServicecategory = 0;// int + dos.writeInt(uServicecategory); + + // RIL_CDMA_SMS_Address + // digit_mode + // number_mode + // number_type + // number_plan + // number_of_digits + // digits[] + CdmaSmsAddress destAddr = CdmaSmsAddress.parse(PhoneNumberUtils + .cdmaCheckAndProcessPlusCode(destinationAddress)); + if (destAddr == null) + return null; + dos.writeByte(destAddr.digitMode);// int + dos.writeByte(destAddr.numberMode);// int + dos.writeByte(destAddr.ton);// int + Log.d(TAG, "message type=" + destAddr.ton + "destination add=" + destinationAddress + + "message=" + message); + dos.writeByte(destAddr.numberPlan);// int + dos.writeByte(destAddr.numberOfDigits);// byte + dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits + + // RIL_CDMA_SMS_Subaddress + // Subaddress is not supported. + dos.writeByte(0); // subaddressType int + dos.writeByte(0); // subaddr_odd byte + dos.writeByte(0); // subaddr_nbr_of_digits byte + + SmsHeader smsHeader = new SmsHeader().fromByteArray(headerStream.toByteArray()); + UserData uData = new UserData(); + uData.payloadStr = message; + // uData.userDataHeader = smsHeader; + uData.msgEncodingSet = true; + uData.msgEncoding = UserData.ENCODING_UNICODE_16; + + BearerData bearerData = new BearerData(); + bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER; + + bearerData.deliveryAckReq = false; + bearerData.userAckReq = false; + bearerData.readAckReq = false; + bearerData.reportReq = false; + + bearerData.userData = uData; + + byte[] encodedBearerData = BearerData.encode(bearerData); + if (null != encodedBearerData) { + // bearer data len + dos.writeByte(encodedBearerData.length);// int + Log.d(TAG, "encodedBearerData length=" + encodedBearerData.length); + + // aBearerData + dos.write(encodedBearerData, 0, encodedBearerData.length); + } else { + dos.writeByte(0); + } + + } catch (IOException e) { + Log.e(TAG, "Error writing dos", e); + } finally { + try { + if (null != byteStream) { + byteStream.close(); + } + + if (null != dos) { + dos.close(); + } + + if (null != headerStream) { + headerStream.close(); + } + } catch (IOException e) { + Log.e(TAG, "Error close dos", e); + } + } + + return byteStream.toByteArray(); + } + + /** + * Generate a Delivery PDU byte array. see getSubmitPdu for reference. + */ + public static byte[] getGsmDeliveryPdu(String scAddress, String destinationAddress, + String message, long date, byte[] header, int encoding) { + // Perform null parameter checks. + if (message == null || destinationAddress == null) { + return null; + } + + // MTI = SMS-DELIVERY, UDHI = header != null + byte mtiByte = (byte)(0x00 | (header != null ? 0x40 : 0x00)); + ByteArrayOutputStream bo = getDeliveryPduHeader(destinationAddress, mtiByte); + // User Data (and length) + byte[] userData; + if (encoding == ENCODING_UNKNOWN) { + // First, try encoding it with the GSM alphabet + encoding = ENCODING_7BIT; + } + try { + if (encoding == ENCODING_7BIT) { + userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header, 0, 0); + } else { //assume UCS-2 + try { + userData = encodeUCS2(message, header); + } catch (UnsupportedEncodingException uex) { + Log.e("GSM", "Implausible UnsupportedEncodingException ", + uex); + return null; + } + } + } catch (EncodeException ex) { + // Encoding to the 7-bit alphabet failed. Let's see if we can + // encode it as a UCS-2 encoded message + try { + userData = encodeUCS2(message, header); + encoding = ENCODING_16BIT; + } catch (UnsupportedEncodingException uex) { + Log.e("GSM", "Implausible UnsupportedEncodingException ", + uex); + return null; + } + } + + if (encoding == ENCODING_7BIT) { + if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) { + // Message too long + return null; + } + bo.write(0x00); + } else { //assume UCS-2 + if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) { + // Message too long + return null; + } + // TP-Data-Coding-Scheme + // Class 3, UCS-2 encoding, uncompressed + bo.write(0x0b); + } + byte[] timestamp = getTimestamp(date); + bo.write(timestamp, 0, timestamp.length); + + bo.write(userData, 0, userData.length); + return bo.toByteArray(); + } + + private static ByteArrayOutputStream getDeliveryPduHeader( + String destinationAddress, byte mtiByte) { + ByteArrayOutputStream bo = new ByteArrayOutputStream( + MAX_USER_DATA_BYTES + 40); + bo.write(mtiByte); + + byte[] daBytes; + daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress); + + if (daBytes == null) { + Log.d(TAG, "The number can not convert to BCD, it's an An alphanumeric address, " + + "destinationAddress = " + destinationAddress); + // Convert address to GSM 7 bit packed bytes. + try { + byte[] numberdata = GsmAlphabet + .stringToGsm7BitPacked(destinationAddress); + // Get the real address data + byte[] addressData = new byte[numberdata.length - 1]; + System.arraycopy(numberdata, 1, addressData, 0, addressData.length); + + daBytes = new byte[addressData.length + OFFSET_ADDRESS_VALUE]; + // Get the address length + int addressLen = numberdata[0]; + daBytes[OFFSET_ADDRESS_LENGTH] = (byte) ((addressLen * 7 % 4 != 0 ? + addressLen * 7 / 4 + 1 : addressLen * 7 / 4)); + // Set address type to Alphanumeric according to 3GPP TS 23.040 [9.1.2.5] + daBytes[OFFSET_TOA] = (byte) 0xd0; + System.arraycopy(addressData, 0, daBytes, OFFSET_ADDRESS_VALUE, addressData.length); + } catch (Exception e) { + Log.e(TAG, "Exception when encoding to 7 bit data."); + } + } else { + // destination address length in BCD digits, ignoring TON byte and pad + // TODO Should be better. + bo.write((daBytes.length - 1) * 2 + - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0)); + } + + // destination address + bo.write(daBytes, 0, daBytes.length); + + // TP-Protocol-Identifier + bo.write(0); + return bo; + } + + private static byte[] encodeUCS2(String message, byte[] header) + throws UnsupportedEncodingException { + byte[] userData, textPart; + textPart = message.getBytes("utf-16be"); + + if (header != null) { + // Need 1 byte for UDHL + userData = new byte[header.length + textPart.length + 1]; + + userData[0] = (byte)header.length; + System.arraycopy(header, 0, userData, 1, header.length); + System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length); + } + else { + userData = textPart; + } + byte[] ret = new byte[userData.length+1]; + ret[0] = (byte) (userData.length & 0xff ); + System.arraycopy(userData, 0, ret, 1, userData.length); + return ret; + } + + private static byte[] getTimestamp(long time) { + // See TS 23.040 9.2.3.11 + byte[] timestamp = new byte[TIMESTAMP_LENGTH]; + SimpleDateFormat sdf = new SimpleDateFormat("yyMMddkkmmss:Z", Locale.US); + String[] date = sdf.format(time).split(":"); + // generate timezone value + String timezone = date[date.length - 1]; + String signMark = timezone.substring(0, 1); + int hour = Integer.parseInt(timezone.substring(1, 3)); + int min = Integer.parseInt(timezone.substring(3)); + int timezoneValue = hour * 4 + min / 15; + // append timezone value to date[0] (time string) + String timestampStr = date[0] + timezoneValue; + + int digitCount = 0; + for (int i = 0; i < timestampStr.length(); i++) { + char c = timestampStr.charAt(i); + int shift = ((digitCount & 0x01) == 1) ? 4 : 0; + timestamp[(digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift); + digitCount++; + } + + if (signMark.equals("-")) { + timestamp[timestamp.length - 1] = (byte) (timestamp[timestamp.length - 1] | 0x08); + } + + return timestamp; + } + + private static int charToBCD(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else { + throw new RuntimeException ("invalid char for BCD " + c); + } + } + @Override public int delete(Uri url, String where, String[] whereArgs) { int count; -- cgit v1.2.3