split long messages with 4-byte unicode characters correctly

also fix SmsMessage.calculateLength() for the same case
refactor to remove some duplicated code

BUG: 19535708
Change-Id: Ie2b46d54fdf09b5979e34d6aa1f7e1cb6dff56f8
diff --git a/src/java/android/telephony/SmsMessage.java b/src/java/android/telephony/SmsMessage.java
index 9749618..3323078 100644
--- a/src/java/android/telephony/SmsMessage.java
+++ b/src/java/android/telephony/SmsMessage.java
@@ -391,7 +391,7 @@
                             ted.languageTable, ted.languageShiftTable);
                 }
             } else {  // Assume unicode.
-                nextPos = pos + Math.min(limit / 2, textLen - pos);
+                nextPos = SmsMessageBase.findNextUnicodePosition(pos, limit, newMsgBody);
             }
             if ((nextPos <= pos) || (nextPos > textLen)) {
                 Rlog.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " +
diff --git a/src/java/com/android/internal/telephony/SmsMessageBase.java b/src/java/com/android/internal/telephony/SmsMessageBase.java
index b027b1e..1d50834 100644
--- a/src/java/com/android/internal/telephony/SmsMessageBase.java
+++ b/src/java/com/android/internal/telephony/SmsMessageBase.java
@@ -16,11 +16,14 @@
 
 package com.android.internal.telephony;
 
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SmsHeader;
+import java.text.BreakIterator;
 import java.util.Arrays;
 
 import android.provider.Telephony;
+import android.telephony.SmsMessage;
 
 /**
  * Base class declaring the specific methods and members for SmsMessage.
@@ -346,4 +349,70 @@
          mIsEmail = Telephony.Mms.isEmailAddress(mEmailFrom);
     }
 
+    /**
+     * Find the next position to start a new fragment of a multipart SMS.
+     *
+     * @param currentPosition current start position of the fragment
+     * @param byteLimit maximum number of bytes in the fragment
+     * @param msgBody text of the SMS in UTF-16 encoding
+     * @return the position to start the next fragment
+     */
+    public static int findNextUnicodePosition(
+            int currentPosition, int byteLimit, CharSequence msgBody) {
+        int nextPos = Math.min(currentPosition + byteLimit / 2, msgBody.length());
+        // Check whether the fragment ends in a character boundary. Some characters take 4-bytes
+        // in UTF-16 encoding. Many carriers cannot handle
+        // a fragment correctly if it does not end at a character boundary.
+        if (nextPos < msgBody.length()) {
+            BreakIterator breakIterator = BreakIterator.getCharacterInstance();
+            breakIterator.setText(msgBody.toString());
+            if (!breakIterator.isBoundary(nextPos)) {
+                nextPos = breakIterator.preceding(nextPos);
+            }
+        }
+        return nextPos;
+    }
+
+    /**
+     * Calculate the TextEncodingDetails of a message encoded in Unicode.
+     */
+    public static TextEncodingDetails calcUnicodeEncodingDetails(CharSequence msgBody) {
+        TextEncodingDetails ted = new TextEncodingDetails();
+        int octets = msgBody.length() * 2;
+        ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
+        ted.codeUnitCount = msgBody.length();
+        if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
+            // If EMS is not supported, break down EMS into single segment SMS
+            // and add page info " x/y".
+            // In the case of UCS2 encoding type, we need 8 bytes for this
+            // but we only have 6 bytes from UDH, so truncate the limit for
+            // each segment by 2 bytes (1 char).
+            int maxUserDataBytesWithHeader = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+            if (!SmsMessage.hasEmsSupport()) {
+                // make sure total number of segments is less than 10
+                if (octets <= 9 * (maxUserDataBytesWithHeader - 2)) {
+                    maxUserDataBytesWithHeader -= 2;
+                }
+            }
+
+            int pos = 0;  // Index in code units.
+            int msgCount = 0;
+            while (pos < msgBody.length()) {
+                int nextPos = findNextUnicodePosition(pos, maxUserDataBytesWithHeader,
+                        msgBody);
+                if (nextPos == msgBody.length()) {
+                    ted.codeUnitsRemaining = pos + maxUserDataBytesWithHeader / 2 -
+                            msgBody.length();
+                }
+                pos = nextPos;
+                msgCount++;
+            }
+            ted.msgCount = msgCount;
+        } else {
+            ted.msgCount = 1;
+            ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets) / 2;
+        }
+
+        return ted;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/cdma/sms/BearerData.java b/src/java/com/android/internal/telephony/cdma/sms/BearerData.java
index 05800b5..58c6e55 100644
--- a/src/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/src/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -24,9 +24,10 @@
 import android.telephony.Rlog;
 
 import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SmsHeader;
-import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.util.BitwiseInputStream;
 import com.android.internal.util.BitwiseOutputStream;
@@ -489,31 +490,7 @@
                     isEntireMsg) {
                 // We don't support single-segment EMS, so calculate for 16-bit
                 // TODO: Consider supporting single-segment EMS
-                ted.codeUnitCount = msg.length();
-                int octets = ted.codeUnitCount * 2;
-                if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
-                    // If EMS is not supported, break down EMS into single segment SMS
-                    // and add page info " x/y".
-                    // In the case of UCS2 encoding type, we need 8 bytes for this
-                    // but we only have 6 bytes from UDH, so truncate the limit for
-                    // each segment by 2 bytes (1 char).
-                    int max_user_data_bytes_with_header =
-                            SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
-                    if (!android.telephony.SmsMessage.hasEmsSupport()) {
-                        // make sure total number of segments is less than 10
-                        if (octets <= 9 * (max_user_data_bytes_with_header - 2))
-                            max_user_data_bytes_with_header -= 2;
-                    }
-
-                    ted.msgCount = (octets + (max_user_data_bytes_with_header - 1)) /
-                            max_user_data_bytes_with_header;
-                    ted.codeUnitsRemaining = ((ted.msgCount *
-                            max_user_data_bytes_with_header) - octets) / 2;
-                } else {
-                    ted.msgCount = 1;
-                    ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets)/2;
-                }
-                ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
+                return SmsMessageBase.calcUnicodeEncodingDetails(msg);
             }
         }
         return ted;
diff --git a/src/java/com/android/internal/telephony/gsm/SmsMessage.java b/src/java/com/android/internal/telephony/gsm/SmsMessage.java
index a1f029f..0d9bed4 100644
--- a/src/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/src/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -777,7 +777,8 @@
     }
 
     /**
-     * Calculate the number of septets needed to encode the message.
+     * Calculates the number of SMS's required to encode the message body and
+     * the number of characters remaining until the next message.
      *
      * @param msgBody the message to encode
      * @param use7bitOnly ignore (but still count) illegal characters if true
@@ -795,31 +796,7 @@
         }
         TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(newMsgBody, use7bitOnly);
         if (ted == null) {
-            ted = new TextEncodingDetails();
-            int octets = newMsgBody.length() * 2;
-            ted.codeUnitCount = newMsgBody.length();
-            if (octets > MAX_USER_DATA_BYTES) {
-                // If EMS is not supported, break down EMS into single segment SMS
-                // and add page info " x/y".
-                // In the case of UCS2 encoding type, we need 8 bytes for this
-                // but we only have 6 bytes from UDH, so truncate the limit for
-                // each segment by 2 bytes (1 char).
-                int max_user_data_bytes_with_header = MAX_USER_DATA_BYTES_WITH_HEADER;
-                if (!android.telephony.SmsMessage.hasEmsSupport()) {
-                    // make sure total number of segments is less than 10
-                    if (octets <= 9 * (max_user_data_bytes_with_header - 2))
-                        max_user_data_bytes_with_header -= 2;
-                }
-
-                ted.msgCount = (octets + (max_user_data_bytes_with_header - 1)) /
-                        max_user_data_bytes_with_header;
-                ted.codeUnitsRemaining = ((ted.msgCount *
-                        max_user_data_bytes_with_header) - octets) / 2;
-            } else {
-                ted.msgCount = 1;
-                ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2;
-            }
-            ted.codeUnitSize = ENCODING_16BIT;
+            return SmsMessageBase.calcUnicodeEncodingDetails(newMsgBody);
         }
         return ted;
     }