Merge "Enable support for multiple SMSDispatchers in CDMALTEPhone."
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 237a892..79995d0 100755
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -666,6 +666,7 @@
             public static SmsMessage[] getMessagesFromIntent(
                     Intent intent) {
                 Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
+                String format = intent.getStringExtra("format");
                 byte[][] pduObjs = new byte[messages.length][];
 
                 for (int i = 0; i < messages.length; i++) {
@@ -676,7 +677,7 @@
                 SmsMessage[] msgs = new SmsMessage[pduCount];
                 for (int i = 0; i < pduCount; i++) {
                     pdus[i] = pduObjs[i];
-                    msgs[i] = SmsMessage.createFromPdu(pdus[i]);
+                    msgs[i] = SmsMessage.createFromPdu(pdus[i], format);
                 }
                 return msgs;
             }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 5bdc146..44bdaeb 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -325,7 +325,7 @@
      *
      * {@hide}
      */
-    public ArrayList<SmsMessage> getAllMessagesFromIcc() {
+    public static ArrayList<SmsMessage> getAllMessagesFromIcc() {
         List<SmsRawData> records = null;
 
         try {
@@ -470,7 +470,7 @@
      *   <code>getAllMessagesFromIcc</code>
      * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
      */
-    private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+    private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
         ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
         if (records != null) {
             int count = records.size();
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index e75d96d..fc8a145 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -36,7 +36,6 @@
  * A Short Message Service message.
  */
 public class SmsMessage {
-    private static final boolean LOCAL_DEBUG = true;
     private static final String LOG_TAG = "SMS";
 
     /**
@@ -78,6 +77,18 @@
      */
     public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
 
+    /**
+     * Indicates a 3GPP format SMS message.
+     * @hide pending API council approval
+     */
+    public static final String FORMAT_3GPP = "3gpp";
+
+    /**
+     * Indicates a 3GPP2 format SMS message.
+     * @hide pending API council approval
+     */
+    public static final String FORMAT_3GPP2 = "3gpp2";
+
     /** Contains actual SmsMessage. Only public for debugging and for framework layer.
      *
      * @hide
@@ -106,30 +117,47 @@
 
     }
 
-    /**
-     * Constructor
-     *
-     * @hide
-     */
-    public SmsMessage() {
-        this(getSmsFacility());
-    }
-
     private SmsMessage(SmsMessageBase smb) {
         mWrappedSmsMessage = smb;
     }
 
     /**
      * Create an SmsMessage from a raw PDU.
+     *
+     * <p><b>This method will soon be deprecated</b> and all applications which handle
+     * incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast
+     * intent <b>must</b> now pass the new {@code format} String extra from the intent
+     * into the new method {@code createFromPdu(byte[], String)} which takes an
+     * extra format parameter. This is required in order to correctly decode the PDU on
+     * devices that require support for both 3GPP and 3GPP2 formats at the same time,
+     * such as dual-mode GSM/CDMA and CDMA/LTE phones.
      */
     public static SmsMessage createFromPdu(byte[] pdu) {
-        SmsMessageBase wrappedMessage;
         int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+        String format = (PHONE_TYPE_CDMA == activePhone) ? FORMAT_3GPP2 : FORMAT_3GPP;
+        return createFromPdu(pdu, format);
+    }
 
-        if (PHONE_TYPE_CDMA == activePhone) {
+    /**
+     * Create an SmsMessage from a raw PDU with the specified message format. The
+     * message format is passed in the {@code SMS_RECEIVED_ACTION} as the {@code format}
+     * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
+     * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
+     *
+     * @param pdu the message PDU from the SMS_RECEIVED_ACTION intent
+     * @param format the format extra from the SMS_RECEIVED_ACTION intent
+     * @hide pending API council approval
+     */
+    public static SmsMessage createFromPdu(byte[] pdu, String format) {
+        SmsMessageBase wrappedMessage;
+
+        if (FORMAT_3GPP2.equals(format)) {
             wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
-        } else {
+        } else if (FORMAT_3GPP.equals(format)) {
             wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
+        } else {
+            Log.e(LOG_TAG, "createFromPdu(): unsupported message format " + format);
+            return null;
         }
 
         return new SmsMessage(wrappedMessage);
@@ -144,57 +172,19 @@
      *
      * {@hide}
      */
-    public static SmsMessage newFromCMT(String[] lines){
-        SmsMessageBase wrappedMessage;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.newFromCMT(lines);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.newFromCMT(lines);
-        }
-
-        return new SmsMessage(wrappedMessage);
-    }
-
-    /** @hide */
-    protected static SmsMessage newFromCMTI(String line) {
-        SmsMessageBase wrappedMessage;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.newFromCMTI(line);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.newFromCMTI(line);
-        }
-
-        return new SmsMessage(wrappedMessage);
-    }
-
-    /** @hide */
-    public static SmsMessage newFromCDS(String line) {
-        SmsMessageBase wrappedMessage;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.newFromCDS(line);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.newFromCDS(line);
-        }
+    public static SmsMessage newFromCMT(String[] lines) {
+        // received SMS in 3GPP format
+        SmsMessageBase wrappedMessage =
+                com.android.internal.telephony.gsm.SmsMessage.newFromCMT(lines);
 
         return new SmsMessage(wrappedMessage);
     }
 
     /** @hide */
     public static SmsMessage newFromParcel(Parcel p) {
-        SmsMessageBase wrappedMessage;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.newFromParcel(p);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.newFromParcel(p);
-        }
+        // received SMS in 3GPP2 format
+        SmsMessageBase wrappedMessage =
+                com.android.internal.telephony.cdma.SmsMessage.newFromParcel(p);
 
         return new SmsMessage(wrappedMessage);
     }
@@ -227,6 +217,9 @@
     /**
      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
      * length in bytes (not hex chars) less the SMSC header
+     *
+     * FIXME: This method is only used by a CTS test case that isn't run on CDMA devices.
+     * We should probably deprecate it and remove the obsolete test case.
      */
     public static int getTPLayerLengthForPDU(String pdu) {
         int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
@@ -381,34 +374,6 @@
      * @return a <code>SubmitPdu</code> containing the encoded SC
      *         address, if applicable, and the encoded message.
      *         Returns null on encode error.
-     * @hide
-     */
-    public static SubmitPdu getSubmitPdu(String scAddress,
-            String destinationAddress, String message,
-            boolean statusReportRequested, byte[] header) {
-        SubmitPduBase spb;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
-                    destinationAddress, message, statusReportRequested,
-                    SmsHeader.fromByteArray(header));
-        } else {
-            spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
-                    destinationAddress, message, statusReportRequested, header);
-        }
-
-        return new SubmitPdu(spb);
-    }
-
-    /**
-     * Get an SMS-SUBMIT PDU for a destination address and a message.
-     * This method will not attempt to use any GSM national language 7 bit encodings.
-     *
-     * @param scAddress Service Centre address.  Null means use default.
-     * @return a <code>SubmitPdu</code> containing the encoded SC
-     *         address, if applicable, and the encoded message.
-     *         Returns null on encode error.
      */
     public static SubmitPdu getSubmitPdu(String scAddress,
             String destinationAddress, String message, boolean statusReportRequested) {
@@ -603,15 +568,6 @@
     }
 
     /**
-     * Return the user data header (UDH).
-     *
-     * @hide
-     */
-    public SmsHeader getUserDataHeader() {
-        return mWrappedSmsMessage.getUserDataHeader();
-    }
-
-    /**
      * Returns the raw PDU for the message.
      *
      * @return the raw PDU for the message.
@@ -646,7 +602,6 @@
      *         SmsManager.STATUS_ON_ICC_UNSENT
      */
     public int getStatusOnIcc() {
-
         return mWrappedSmsMessage.getStatusOnIcc();
     }
 
@@ -666,7 +621,6 @@
      *         SmsMessage was not created from a ICC SMS EF record.
      */
     public int getIndexOnIcc() {
-
         return mWrappedSmsMessage.getIndexOnIcc();
     }
 
@@ -704,19 +658,4 @@
     public boolean isReplyPathPresent() {
         return mWrappedSmsMessage.isReplyPathPresent();
     }
-
-    /** This method returns the reference to a specific
-     *  SmsMessage object, which is used for accessing its static methods.
-     * @return Specific SmsMessage.
-     *
-     * @hide
-     */
-    private static final SmsMessageBase getSmsFacility(){
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-        if (PHONE_TYPE_CDMA == activePhone) {
-            return new com.android.internal.telephony.cdma.SmsMessage();
-        } else {
-            return new com.android.internal.telephony.gsm.SmsMessage();
-        }
-    }
 }
diff --git a/telephony/java/android/telephony/gsm/SmsMessage.java b/telephony/java/android/telephony/gsm/SmsMessage.java
index 4af99a6..8d86ec2 100644
--- a/telephony/java/android/telephony/gsm/SmsMessage.java
+++ b/telephony/java/android/telephony/gsm/SmsMessage.java
@@ -166,104 +166,6 @@
     }
 
     /**
-     * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
-     * +CMT unsolicited response (PDU mode, of course)
-     *  +CMT: [&lt;alpha>],<length><CR><LF><pdu>
-     *
-     * Only public for debugging and for RIL
-     * @deprecated Use android.telephony.SmsMessage.
-     * {@hide}
-     */
-    @Deprecated
-    public static SmsMessage newFromCMT(String[] lines){
-        SmsMessageBase wrappedMessage;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.newFromCMT(lines);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.newFromCMT(lines);
-        }
-
-        return new SmsMessage(wrappedMessage);
-    }
-
-    /** @deprecated Use android.telephony.SmsMessage.
-     *  @hide */
-    @Deprecated
-    protected static SmsMessage newFromCMTI(String line) {
-        SmsMessageBase wrappedMessage;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.newFromCMTI(line);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.newFromCMTI(line);
-        }
-
-        return new SmsMessage(wrappedMessage);
-    }
-
-    /** @deprecated Use android.telephony.SmsMessage.
-     *  @hide */
-    @Deprecated
-    public static SmsMessage newFromCDS(String line) {
-        SmsMessageBase wrappedMessage;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.newFromCDS(line);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.newFromCDS(line);
-        }
-
-        return new SmsMessage(wrappedMessage);
-    }
-
-    /** @deprecated Use android.telephony.SmsMessage.
-     *  @hide */
-    @Deprecated
-    public static SmsMessage newFromParcel(Parcel p) {
-        SmsMessageBase wrappedMessage;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.newFromParcel(p);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.newFromParcel(p);
-        }
-
-        return new SmsMessage(wrappedMessage);
-    }
-
-    /**
-     * Create an SmsMessage from an SMS EF record.
-     *
-     * @param index Index of SMS record. This should be index in ArrayList
-     *              returned by SmsManager.getAllMessagesFromSim + 1.
-     * @param data Record data.
-     * @return An SmsMessage representing the record.
-     *
-     * @deprecated Use android.telephony.SmsMessage.
-     * @hide
-     */
-    @Deprecated
-    public static SmsMessage createFromEfRecord(int index, byte[] data) {
-        SmsMessageBase wrappedMessage;
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
-
-        if (PHONE_TYPE_CDMA == activePhone) {
-            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
-                    index, data);
-        } else {
-            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
-                    index, data);
-        }
-
-        return new SmsMessage(wrappedMessage);
-    }
-
-    /**
      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
      * length in bytes (not hex chars) less the SMSC header
      * @deprecated Use android.telephony.SmsMessage.
diff --git a/telephony/java/com/android/internal/telephony/BaseCommands.java b/telephony/java/com/android/internal/telephony/BaseCommands.java
index f0d2fba..f111dd6 100644
--- a/telephony/java/com/android/internal/telephony/BaseCommands.java
+++ b/telephony/java/com/android/internal/telephony/BaseCommands.java
@@ -79,7 +79,8 @@
     protected RegistrantList mRilConnectedRegistrants = new RegistrantList();
     protected RegistrantList mIccRefreshRegistrants = new RegistrantList();
 
-    protected Registrant mSMSRegistrant;
+    protected Registrant mGsmSmsRegistrant;
+    protected Registrant mCdmaSmsRegistrant;
     protected Registrant mNITZTimeRegistrant;
     protected Registrant mSignalStrengthRegistrant;
     protected Registrant mUSSDRegistrant;
@@ -358,12 +359,20 @@
         mIccStatusChangedRegistrants.remove(h);
     }
 
-    public void setOnNewSMS(Handler h, int what, Object obj) {
-        mSMSRegistrant = new Registrant (h, what, obj);
+    public void setOnNewGsmSms(Handler h, int what, Object obj) {
+        mGsmSmsRegistrant = new Registrant (h, what, obj);
     }
 
-    public void unSetOnNewSMS(Handler h) {
-        mSMSRegistrant.clear();
+    public void unSetOnNewGsmSms(Handler h) {
+        mGsmSmsRegistrant.clear();
+    }
+
+    public void setOnNewCdmaSms(Handler h, int what, Object obj) {
+        mCdmaSmsRegistrant = new Registrant (h, what, obj);
+    }
+
+    public void unSetOnNewCdmaSms(Handler h) {
+        mCdmaSmsRegistrant.clear();
     }
 
     public void setOnNewGsmBroadcastSms(Handler h, int what, Object obj) {
diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java
index 1caea70..33eed38 100644
--- a/telephony/java/com/android/internal/telephony/CommandsInterface.java
+++ b/telephony/java/com/android/internal/telephony/CommandsInterface.java
@@ -20,8 +20,6 @@
 
 import android.os.Message;
 import android.os.Handler;
-import android.os.SystemProperties;
-
 
 /**
  * {@hide}
@@ -267,14 +265,32 @@
     void unregisterForRUIMReady(Handler h);
 
     /**
-     * unlike the register* methods, there's only one new SMS handler
+     * unlike the register* methods, there's only one new 3GPP format SMS handler.
      * if you need to unregister, you should also tell the radio to stop
      * sending SMS's to you (via AT+CNMI)
      *
      * AsyncResult.result is a String containing the SMS PDU
      */
-    void setOnNewSMS(Handler h, int what, Object obj);
-    void unSetOnNewSMS(Handler h);
+    void setOnNewGsmSms(Handler h, int what, Object obj);
+    void unSetOnNewGsmSms(Handler h);
+
+    /**
+     * unlike the register* methods, there's only one new 3GPP2 format SMS handler.
+     * if you need to unregister, you should also tell the radio to stop
+     * sending SMS's to you (via AT+CNMI)
+     *
+     * AsyncResult.result is a String containing the SMS PDU
+     */
+    void setOnNewCdmaSms(Handler h, int what, Object obj);
+    void unSetOnNewCdmaSms(Handler h);
+
+    /**
+     * Set the handler for SMS Cell Broadcast messages.
+     *
+     * AsyncResult.result is a byte array containing the SMS-CB PDU
+     */
+    void setOnNewGsmBroadcastSms(Handler h, int what, Object obj);
+    void unSetOnNewGsmBroadcastSms(Handler h);
 
    /**
      * Register for NEW_SMS_ON_SIM unsolicited message
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index 444f0d2..ca04eb2 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -1394,7 +1394,7 @@
     String getDeviceSvn();
 
     /**
-     * Retrieves the unique sbuscriber ID, e.g., IMSI for GSM phones.
+     * Retrieves the unique subscriber ID, e.g., IMSI for GSM phones.
      */
     String getSubscriberId();
 
@@ -1756,4 +1756,13 @@
      * @param response a callback message with the String response in the obj field
      */
     void requestIsimAuthentication(String nonce, Message response);
+
+    /**
+     * Sets the SIM voice message waiting indicator records.
+     * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported
+     * @param countWaiting The number of messages waiting, if known. Use
+     *                     -1 to indicate that an unknown number of
+     *                      messages are waiting
+     */
+    void setVoiceMessageWaiting(int line, int countWaiting);
 }
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index 82f3955..a7a4908 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -112,15 +112,17 @@
     /* Instance Variables */
     public CommandsInterface mCM;
     protected IccFileHandler mIccFileHandler;
-    boolean mDnsCheckDisabled = false;
+    boolean mDnsCheckDisabled;
     public DataConnectionTracker mDataConnectionTracker;
     boolean mDoesRilSendMultipleCallRing;
-    int mCallRingContinueToken = 0;
+    int mCallRingContinueToken;
     int mCallRingDelay;
     public boolean mIsTheCurrentActivePhone = true;
     boolean mIsVoiceCapable = true;
     public IccRecords mIccRecords;
     public IccCard mIccCard;
+    public SmsStorageMonitor mSmsStorageMonitor;
+    public SmsUsageMonitor mSmsUsageMonitor;
     public SMSDispatcher mSMS;
 
     /**
@@ -164,7 +166,7 @@
 
     protected Looper mLooper; /* to insure registrants are in correct thread*/
 
-    protected Context mContext;
+    protected final Context mContext;
 
     /**
      * PhoneNotifier is an abstraction for all system-wide
@@ -238,6 +240,10 @@
         mCallRingDelay = SystemProperties.getInt(
                 TelephonyProperties.PROPERTY_CALL_RING_DELAY, 3000);
         Log.d(LOG_TAG, "mCallRingDelay=" + mCallRingDelay);
+
+        // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
+        mSmsStorageMonitor = new SmsStorageMonitor(this);
+        mSmsUsageMonitor = new SmsUsageMonitor(context.getContentResolver());
     }
 
     public void dispose() {
@@ -246,9 +252,17 @@
             // Must cleanup all connectionS and needs to use sendMessage!
             mDataConnectionTracker.cleanUpAllConnections(null);
             mIsTheCurrentActivePhone = false;
+            // Dispose the SMS usage and storage monitors
+            mSmsStorageMonitor.dispose();
+            mSmsUsageMonitor.dispose();
         }
     }
 
+    public void removeReferences() {
+        mSmsStorageMonitor = null;
+        mSmsUsageMonitor = null;
+    }
+
     /**
      * When overridden the derived class needs to call
      * super.handleMessage(msg) so this method has a
@@ -1037,37 +1051,6 @@
     }
 
     /**
-     * simulateDataConnection
-     *
-     * simulates various data connection states. This messes with
-     * DataConnectionTracker's internal states, but doesn't actually change
-     * the underlying radio connection states.
-     *
-     * @param state Phone.DataState enum.
-     */
-    public void simulateDataConnection(Phone.DataState state) {
-        DataConnectionTracker.State dcState;
-
-        switch (state) {
-            case CONNECTED:
-                dcState = DataConnectionTracker.State.CONNECTED;
-                break;
-            case SUSPENDED:
-                dcState = DataConnectionTracker.State.CONNECTED;
-                break;
-            case DISCONNECTED:
-                dcState = DataConnectionTracker.State.FAILED;
-                break;
-            default:
-                dcState = DataConnectionTracker.State.CONNECTING;
-                break;
-        }
-
-        mDataConnectionTracker.setState(dcState);
-        notifyDataConnection(null, Phone.APN_TYPE_DEFAULT);
-    }
-
-    /**
      * Notify registrants of a new ringing Connection.
      * Subclasses of Phone probably want to replace this with a
      * version scoped to their packages
@@ -1132,7 +1115,7 @@
     /**
      * Common error logger method for unexpected calls to CDMA-only methods.
      */
-    private void logUnexpectedCdmaMethodCall(String name)
+    private static void logUnexpectedCdmaMethodCall(String name)
     {
         Log.e(LOG_TAG, "Error! " + name + "() in PhoneBase should not be " +
                 "called, CDMAPhone inactive.");
@@ -1145,7 +1128,7 @@
     /**
      * Common error logger method for unexpected calls to GSM/WCDMA-only methods.
      */
-    private void logUnexpectedGsmMethodCall(String name) {
+    private static void logUnexpectedGsmMethodCall(String name) {
         Log.e(LOG_TAG, "Error! " + name + "() in PhoneBase should not be " +
                 "called, GSMPhone inactive.");
     }
@@ -1167,4 +1150,16 @@
     public int getLteOnCdmaMode() {
         return mCM.getLteOnCdmaMode();
     }
+
+    /**
+     * Sets the SIM voice message waiting indicator records.
+     * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported
+     * @param countWaiting The number of messages waiting, if known. Use
+     *                     -1 to indicate that an unknown number of
+     *                      messages are waiting
+     */
+    @Override
+    public void setVoiceMessageWaiting(int line, int countWaiting) {
+        mIccRecords.setVoiceMessageWaiting(line, countWaiting);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java
index e0e8d49..b497ec8 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -72,7 +72,7 @@
         switch(msg.what) {
         case EVENT_RADIO_TECHNOLOGY_CHANGED:
             //switch Phone from CDMA to GSM or vice versa
-            mOutgoingPhone = ((PhoneBase)mActivePhone).getPhoneName();
+            mOutgoingPhone = mActivePhone.getPhoneName();
             logd("Switching phone from " + mOutgoingPhone + "Phone to " +
                     (mOutgoingPhone.equals("GSM") ? "CDMAPhone" : "GSMPhone") );
             boolean oldPowerState = false; // old power state to off
@@ -144,23 +144,10 @@
         super.handleMessage(msg);
     }
 
-    private void logv(String msg) {
-        Log.v(LOG_TAG, "[PhoneProxy] " + msg);
-    }
-
-    private void logd(String msg) {
+    private static void logd(String msg) {
         Log.d(LOG_TAG, "[PhoneProxy] " + msg);
     }
 
-    private void logw(String msg) {
-        Log.w(LOG_TAG, "[PhoneProxy] " + msg);
-    }
-
-    private void loge(String msg) {
-        Log.e(LOG_TAG, "[PhoneProxy] " + msg);
-    }
-
-
     public ServiceState getServiceState() {
         return mActivePhone.getServiceState();
     }
@@ -739,19 +726,19 @@
     }
 
     public int getCdmaEriIconIndex() {
-         return mActivePhone.getCdmaEriIconIndex();
+        return mActivePhone.getCdmaEriIconIndex();
     }
 
-     public String getCdmaEriText() {
-         return mActivePhone.getCdmaEriText();
-     }
+    public String getCdmaEriText() {
+        return mActivePhone.getCdmaEriText();
+    }
 
     public int getCdmaEriIconMode() {
-         return mActivePhone.getCdmaEriIconMode();
+        return mActivePhone.getCdmaEriIconMode();
     }
 
     public Phone getActivePhone() {
-         return mActivePhone;
+        return mActivePhone;
     }
 
     public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete){
@@ -861,4 +848,9 @@
     public int getLteOnCdmaMode() {
         return mActivePhone.getLteOnCdmaMode();
     }
+
+    @Override
+    public void setVoiceMessageWaiting(int line, int countWaiting) {
+        mActivePhone.setVoiceMessageWaiting(line, countWaiting);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index bd35058..8aae0d4 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -2434,8 +2434,8 @@
                 SmsMessage sms;
 
                 sms = SmsMessage.newFromCMT(a);
-                if (mSMSRegistrant != null) {
-                    mSMSRegistrant
+                if (mGsmSmsRegistrant != null) {
+                    mGsmSmsRegistrant
                         .notifyRegistrant(new AsyncResult(null, sms, null));
                 }
             break;
@@ -2607,8 +2607,8 @@
 
                 SmsMessage sms = (SmsMessage) ret;
 
-                if (mSMSRegistrant != null) {
-                    mSMSRegistrant
+                if (mCdmaSmsRegistrant != null) {
+                    mCdmaSmsRegistrant
                         .notifyRegistrant(new AsyncResult(null, sms, null));
                 }
                 break;
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index 76e719c..e4c6028 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -44,10 +44,12 @@
 import android.util.Log;
 import android.view.WindowManager;
 
+import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
 import com.android.internal.util.HexDump;
 
 import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Random;
 
@@ -60,68 +62,66 @@
 import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
 import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
 
-
 public abstract class SMSDispatcher extends Handler {
-    private static final String TAG = "SMS";
+    static final String TAG = "SMS";    // accessed from inner class
     private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
 
-    /** Default checking period for SMS sent without user permit */
-    private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000;
-
-    /** Default number of SMS sent in checking period without user permit */
-    private static final int DEFAULT_SMS_MAX_COUNT = 100;
-
     /** Default timeout for SMS sent query */
     private static final int DEFAULT_SMS_TIMEOUT = 6000;
 
-    protected static final String[] RAW_PROJECTION = new String[] {
-        "pdu",
-        "sequence",
-        "destination_port",
+    /** Permission required to receive SMS and SMS-CB messages. */
+    public static final String RECEIVE_SMS_PERMISSION = "android.permission.RECEIVE_SMS";
+
+    /** Permission required to receive ETWS and CMAS emergency broadcasts. */
+    public static final String RECEIVE_EMERGENCY_BROADCAST_PERMISSION =
+            "android.permission.RECEIVE_EMERGENCY_BROADCAST";
+
+    /** Query projection for checking for duplicate message segments. */
+    private static final String[] PDU_PROJECTION = new String[] {
+            "pdu"
     };
 
-    static final protected int EVENT_NEW_SMS = 1;
+    /** Query projection for combining concatenated message segments. */
+    private static final String[] PDU_SEQUENCE_PORT_PROJECTION = new String[] {
+            "pdu",
+            "sequence",
+            "destination_port"
+    };
 
-    static final protected int EVENT_SEND_SMS_COMPLETE = 2;
+    private static final int PDU_COLUMN = 0;
+    private static final int SEQUENCE_COLUMN = 1;
+    private static final int DESTINATION_PORT_COLUMN = 2;
+
+    /** New SMS received. */
+    protected static final int EVENT_NEW_SMS = 1;
+
+    /** SMS send complete. */
+    protected static final int EVENT_SEND_SMS_COMPLETE = 2;
 
     /** Retry sending a previously failed SMS message */
-    static final protected int EVENT_SEND_RETRY = 3;
-
-    /** Status report received */
-    static final protected int EVENT_NEW_SMS_STATUS_REPORT = 5;
-
-    /** SIM/RUIM storage is full */
-    static final protected int EVENT_ICC_FULL = 6;
+    private static final int EVENT_SEND_RETRY = 3;
 
     /** SMS confirm required */
-    static final protected int EVENT_POST_ALERT = 7;
+    private static final int EVENT_POST_ALERT = 4;
 
     /** Send the user confirmed SMS */
-    static final protected int EVENT_SEND_CONFIRMED_SMS = 8;
+    static final int EVENT_SEND_CONFIRMED_SMS = 5;  // accessed from inner class
 
     /** Alert is timeout */
-    static final protected int EVENT_ALERT_TIMEOUT = 9;
+    private static final int EVENT_ALERT_TIMEOUT = 6;
 
     /** Stop the sending */
-    static final protected int EVENT_STOP_SENDING = 10;
+    static final int EVENT_STOP_SENDING = 7;        // accessed from inner class
 
-    /** Memory status reporting is acknowledged by RIL */
-    static final protected int EVENT_REPORT_MEMORY_STATUS_DONE = 11;
-
-    /** Radio is ON */
-    static final protected int EVENT_RADIO_ON = 12;
-
-    /** New broadcast SMS */
-    static final protected int EVENT_NEW_BROADCAST_SMS = 13;
-
-    protected Phone mPhone;
-    protected Context mContext;
-    protected ContentResolver mResolver;
-    protected CommandsInterface mCm;
+    protected final Phone mPhone;
+    protected final Context mContext;
+    protected final ContentResolver mResolver;
+    protected final CommandsInterface mCm;
+    protected final SmsStorageMonitor mStorageMonitor;
 
     protected final WapPushOverSms mWapPush;
 
-    protected final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
+    protected static final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
 
     /** Maximum number of times to retry sending a failed SMS. */
     private static final int MAX_SEND_RETRIES = 3;
@@ -136,12 +136,14 @@
      * Message reference for a CONCATENATED_8_BIT_REFERENCE or
      * CONCATENATED_16_BIT_REFERENCE message set.  Should be
      * incremented for each set of concatenated messages.
+     * Static field shared by all dispatcher objects.
      */
-    private static int sConcatenatedRef;
+    private static int sConcatenatedRef = new Random().nextInt(256);
 
-    private SmsCounter mCounter;
+    /** Outgoing message counter. Shared by all dispatchers. */
+    private final SmsUsageMonitor mUsageMonitor;
 
-    private ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT);
+    private final ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT);
 
     /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
     private PowerManager.WakeLock mWakeLock;
@@ -150,17 +152,14 @@
      * Hold the wake lock for 5 seconds, which should be enough time for
      * any receiver(s) to grab its own wake lock.
      */
-    private final int WAKE_LOCK_TIMEOUT = 5000;
-
-    protected boolean mStorageAvailable = true;
-    protected boolean mReportMemoryStatusPending = false;
+    private static final int WAKE_LOCK_TIMEOUT = 5000;
 
     /* Flags indicating whether the current device allows sms service */
     protected boolean mSmsCapable = true;
     protected boolean mSmsReceiveDisabled;
     protected boolean mSmsSendDisabled;
 
-    protected static int mRemainingMessages = -1;
+    protected int mRemainingMessages = -1;
 
     protected static int getNextConcatenatedRef() {
         sConcatenatedRef += 1;
@@ -168,111 +167,52 @@
     }
 
     /**
-     *  Implement the per-application based SMS control, which only allows
-     *  a limit on the number of SMS/MMS messages an app can send in checking
-     *  period.
+     * Create a new SMS dispatcher.
+     * @param phone the Phone to use
+     * @param storageMonitor the SmsStorageMonitor to use
+     * @param usageMonitor the SmsUsageMonitor to use
      */
-    private class SmsCounter {
-        private int mCheckPeriod;
-        private int mMaxAllowed;
-        private HashMap<String, ArrayList<Long>> mSmsStamp;
-
-        /**
-         * Create SmsCounter
-         * @param mMax is the number of SMS allowed without user permit
-         * @param mPeriod is the checking period
-         */
-        SmsCounter(int mMax, int mPeriod) {
-            mMaxAllowed = mMax;
-            mCheckPeriod = mPeriod;
-            mSmsStamp = new HashMap<String, ArrayList<Long>> ();
-        }
-
-        /**
-         * Check to see if an application allow to send new SMS messages
-         *
-         * @param appName is the application sending sms
-         * @param smsWaiting is the number of new sms wants to be sent
-         * @return true if application is allowed to send the requested number
-         *         of new sms messages
-         */
-        boolean check(String appName, int smsWaiting) {
-            if (!mSmsStamp.containsKey(appName)) {
-                mSmsStamp.put(appName, new ArrayList<Long>());
-            }
-
-            return isUnderLimit(mSmsStamp.get(appName), smsWaiting);
-        }
-
-        private boolean isUnderLimit(ArrayList<Long> sent, int smsWaiting) {
-            Long ct =  System.currentTimeMillis();
-
-            Log.d(TAG, "SMS send size=" + sent.size() + "time=" + ct);
-
-            while (sent.size() > 0 && (ct - sent.get(0)) > mCheckPeriod ) {
-                    sent.remove(0);
-            }
-
-
-            if ( (sent.size() + smsWaiting) <= mMaxAllowed) {
-                for (int i = 0; i < smsWaiting; i++ ) {
-                    sent.add(ct);
-                }
-                return true;
-            }
-            return false;
-        }
-    }
-
-    protected SMSDispatcher(PhoneBase phone) {
+    protected SMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
+            SmsUsageMonitor usageMonitor) {
         mPhone = phone;
         mWapPush = new WapPushOverSms(phone, this);
         mContext = phone.getContext();
         mResolver = mContext.getContentResolver();
         mCm = phone.mCM;
+        mStorageMonitor = storageMonitor;
+        mUsageMonitor = usageMonitor;
 
         createWakelock();
 
-        int check_period = Settings.Secure.getInt(mResolver,
-                Settings.Secure.SMS_OUTGOING_CHECK_INTERVAL_MS,
-                DEFAULT_SMS_CHECK_PERIOD);
-        int max_count = Settings.Secure.getInt(mResolver,
-                Settings.Secure.SMS_OUTGOING_CHECK_MAX_COUNT,
-                DEFAULT_SMS_MAX_COUNT);
-        mCounter = new SmsCounter(max_count, check_period);
-
-        mCm.setOnNewSMS(this, EVENT_NEW_SMS, null);
-        mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
-        mCm.setOnIccSmsFull(this, EVENT_ICC_FULL, null);
-        mCm.registerForOn(this, EVENT_RADIO_ON, null);
-
-        // Don't always start message ref at 0.
-        sConcatenatedRef = new Random().nextInt(256);
-
-        // Register for device storage intents.  Use these to notify the RIL
-        // that storage for SMS is or is not available.
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_DEVICE_STORAGE_FULL);
-        filter.addAction(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
-        mContext.registerReceiver(mResultReceiver, filter);
-
         mSmsCapable = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_sms_capable);
         mSmsReceiveDisabled = !SystemProperties.getBoolean(
                                 TelephonyProperties.PROPERTY_SMS_RECEIVE, mSmsCapable);
         mSmsSendDisabled = !SystemProperties.getBoolean(
                                 TelephonyProperties.PROPERTY_SMS_SEND, mSmsCapable);
-        Log.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable
+        Log.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable + " format=" + getFormat()
                 + " mSmsReceiveDisabled=" + mSmsReceiveDisabled
                 + " mSmsSendDisabled=" + mSmsSendDisabled);
     }
 
-    public void dispose() {
-        mCm.unSetOnNewSMS(this);
-        mCm.unSetOnSmsStatus(this);
-        mCm.unSetOnIccSmsFull(this);
-        mCm.unregisterForOn(this);
-    }
+    /** Unregister for incoming SMS events. */
+    public abstract void dispose();
+
+    /**
+     * The format of the message PDU in the associated broadcast intent.
+     * This will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
+     * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
+     *
+     * Note: All applications which handle incoming SMS messages by processing the
+     * SMS_RECEIVED_ACTION broadcast intent MUST pass the "format" extra from the intent
+     * into the new methods in {@link android.telephony.SmsMessage} which take an
+     * extra format parameter. This is required in order to correctly decode the PDU on
+     * devices which require support for both 3GPP and 3GPP2 formats at the same time,
+     * such as CDMA/LTE devices and GSM/CDMA world phones.
+     *
+     * @return the format of the message PDU
+     */
+    protected abstract String getFormat();
 
     @Override
     protected void finalize() {
@@ -338,14 +278,6 @@
             sendSms((SmsTracker) msg.obj);
             break;
 
-        case EVENT_NEW_SMS_STATUS_REPORT:
-            handleStatusReport((AsyncResult)msg.obj);
-            break;
-
-        case EVENT_ICC_FULL:
-            handleIccFull();
-            break;
-
         case EVENT_POST_ALERT:
             handleReachSentLimit((SmsTracker)(msg.obj));
             break;
@@ -369,7 +301,7 @@
         case EVENT_SEND_CONFIRMED_SMS:
             if (mSTrackers.isEmpty() == false) {
                 SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
-                if (isMultipartTracker(sTracker)) {
+                if (sTracker.isMultipart()) {
                     sendMultipartSms(sTracker);
                 } else {
                     sendSms(sTracker);
@@ -390,30 +322,6 @@
                 removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
             }
             break;
-
-        case EVENT_REPORT_MEMORY_STATUS_DONE:
-            ar = (AsyncResult)msg.obj;
-            if (ar.exception != null) {
-                mReportMemoryStatusPending = true;
-                Log.v(TAG, "Memory status report to modem pending : mStorageAvailable = "
-                        + mStorageAvailable);
-            } else {
-                mReportMemoryStatusPending = false;
-            }
-            break;
-
-        case EVENT_RADIO_ON:
-            if (mReportMemoryStatusPending) {
-                Log.v(TAG, "Sending pending memory status report : mStorageAvailable = "
-                        + mStorageAvailable);
-                mCm.reportSmsMemoryStatus(mStorageAvailable,
-                        obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
-            }
-            break;
-
-        case EVENT_NEW_BROADCAST_SMS:
-            handleBroadcastSms((AsyncResult)msg.obj);
-            break;
         }
     }
 
@@ -440,26 +348,6 @@
     }
 
     /**
-     * Called when SIM_FULL message is received from the RIL.  Notifies interested
-     * parties that SIM storage for SMS messages is full.
-     */
-    private void handleIccFull(){
-        // broadcast SIM_FULL intent
-        Intent intent = new Intent(Intents.SIM_FULL_ACTION);
-        mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
-        mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
-    }
-
-    /**
-     * Called when a status report is received.  This should correspond to
-     * a previously successful SEND.
-     *
-     * @param ar AsyncResult passed into the message handler.  ar.result should
-     *           be a String representing the status report PDU, as ASCII hex.
-     */
-    protected abstract void handleStatusReport(AsyncResult ar);
-
-    /**
      * Called when SMS send completes. Broadcasts a sentIntent on success.
      * On failure, either sets up retries or broadcasts a sentIntent with
      * the failure in the result code.
@@ -559,7 +447,7 @@
      *                  POWER_OFF
      * @param tracker   An SmsTracker for the current message.
      */
-    protected void handleNotInService(int ss, SmsTracker tracker) {
+    protected static void handleNotInService(int ss, SmsTracker tracker) {
         if (tracker.mSentIntent != null) {
             try {
                 if (ss == ServiceState.STATE_POWER_OFF) {
@@ -581,86 +469,171 @@
      */
     public abstract int dispatchMessage(SmsMessageBase sms);
 
+    /**
+     * Dispatch a normal incoming SMS. This is called from the format-specific
+     * {@link #dispatchMessage(SmsMessageBase)} if no format-specific handling is required.
+     *
+     * @param sms
+     * @return
+     */
+    protected int dispatchNormalMessage(SmsMessageBase sms) {
+        SmsHeader smsHeader = sms.getUserDataHeader();
+
+        // See if message is partial or port addressed.
+        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
+            // Message is not partial (not part of concatenated sequence).
+            byte[][] pdus = new byte[1][];
+            pdus[0] = sms.getPdu();
+
+            if (smsHeader != null && smsHeader.portAddrs != null) {
+                if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
+                    // GSM-style WAP indication
+                    return mWapPush.dispatchWapPdu(sms.getUserData());
+                } else {
+                    // The message was sent to a port, so concoct a URI for it.
+                    dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
+                }
+            } else {
+                // Normal short and non-port-addressed message, dispatch it.
+                dispatchPdus(pdus);
+            }
+            return Activity.RESULT_OK;
+        } else {
+            // Process the message part.
+            SmsHeader.ConcatRef concatRef = smsHeader.concatRef;
+            SmsHeader.PortAddrs portAddrs = smsHeader.portAddrs;
+            return processMessagePart(sms.getPdu(), sms.getOriginatingAddress(),
+                    concatRef.refNumber, concatRef.seqNumber, concatRef.msgCount,
+                    sms.getTimestampMillis(), (portAddrs != null ? portAddrs.destPort : -1), false);
+        }
+    }
 
     /**
      * If this is the last part send the parts out to the application, otherwise
-     * the part is stored for later processing.
+     * the part is stored for later processing. Handles both 3GPP concatenated messages
+     * as well as 3GPP2 format WAP push messages processed by
+     * {@link com.android.internal.telephony.cdma.CdmaSMSDispatcher#processCdmaWapPdu}.
      *
-     * NOTE: concatRef (naturally) needs to be non-null, but portAddrs can be null.
+     * @param pdu the message PDU, or the datagram portion of a CDMA WDP datagram segment
+     * @param address the originating address
+     * @param referenceNumber distinguishes concatenated messages from the same sender
+     * @param sequenceNumber the order of this segment in the message
+     * @param messageCount the number of segments in the message
+     * @param timestamp the service center timestamp in millis
+     * @param destPort the destination port for the message, or -1 for no destination port
+     * @param isCdmaWapPush true if pdu is a CDMA WDP datagram segment and not an SM PDU
+     *
      * @return a result code from {@link Telephony.Sms.Intents}, or
      *         {@link Activity#RESULT_OK} if the message has been broadcast
      *         to applications
      */
-    protected int processMessagePart(SmsMessageBase sms,
-            SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) {
-
-        // Lookup all other related parts
-        StringBuilder where = new StringBuilder("reference_number =");
-        where.append(concatRef.refNumber);
-        where.append(" AND address = ?");
-        String[] whereArgs = new String[] {sms.getOriginatingAddress()};
-
+    protected int processMessagePart(byte[] pdu, String address, int referenceNumber,
+            int sequenceNumber, int messageCount, long timestamp, int destPort,
+            boolean isCdmaWapPush) {
         byte[][] pdus = null;
         Cursor cursor = null;
         try {
-            cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
+            // used by several query selection arguments
+            String refNumber = Integer.toString(referenceNumber);
+            String seqNumber = Integer.toString(sequenceNumber);
+
+            // Check for duplicate message segment
+            cursor = mResolver.query(mRawUri, PDU_PROJECTION,
+                    "address=? AND reference_number=? AND sequence=?",
+                    new String[] {address, refNumber, seqNumber}, null);
+
+            // moveToNext() returns false if no duplicates were found
+            if (cursor.moveToNext()) {
+                Log.w(TAG, "Discarding duplicate message segment from address=" + address
+                        + " refNumber=" + refNumber + " seqNumber=" + seqNumber);
+                String oldPduString = cursor.getString(PDU_COLUMN);
+                byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString);
+                if (!Arrays.equals(oldPdu, pdu)) {
+                    Log.e(TAG, "Warning: dup message segment PDU of length " + pdu.length
+                            + " is different from existing PDU of length " + oldPdu.length);
+                }
+                return Intents.RESULT_SMS_HANDLED;
+            }
+            cursor.close();
+
+            // not a dup, query for all other segments of this concatenated message
+            String where = "address=? AND reference_number=?";
+            String[] whereArgs = new String[] {address, refNumber};
+            cursor = mResolver.query(mRawUri, PDU_SEQUENCE_PORT_PROJECTION, where, whereArgs, null);
+
             int cursorCount = cursor.getCount();
-            if (cursorCount != concatRef.msgCount - 1) {
+            if (cursorCount != messageCount - 1) {
                 // We don't have all the parts yet, store this one away
                 ContentValues values = new ContentValues();
-                values.put("date", new Long(sms.getTimestampMillis()));
-                values.put("pdu", HexDump.toHexString(sms.getPdu()));
-                values.put("address", sms.getOriginatingAddress());
-                values.put("reference_number", concatRef.refNumber);
-                values.put("count", concatRef.msgCount);
-                values.put("sequence", concatRef.seqNumber);
-                if (portAddrs != null) {
-                    values.put("destination_port", portAddrs.destPort);
+                values.put("date", timestamp);
+                values.put("pdu", HexDump.toHexString(pdu));
+                values.put("address", address);
+                values.put("reference_number", referenceNumber);
+                values.put("count", messageCount);
+                values.put("sequence", sequenceNumber);
+                if (destPort != -1) {
+                    values.put("destination_port", destPort);
                 }
                 mResolver.insert(mRawUri, values);
                 return Intents.RESULT_SMS_HANDLED;
             }
 
             // All the parts are in place, deal with them
-            int pduColumn = cursor.getColumnIndex("pdu");
-            int sequenceColumn = cursor.getColumnIndex("sequence");
-
-            pdus = new byte[concatRef.msgCount][];
+            pdus = new byte[messageCount][];
             for (int i = 0; i < cursorCount; i++) {
                 cursor.moveToNext();
-                int cursorSequence = (int)cursor.getLong(sequenceColumn);
+                int cursorSequence = cursor.getInt(SEQUENCE_COLUMN);
                 pdus[cursorSequence - 1] = HexDump.hexStringToByteArray(
-                        cursor.getString(pduColumn));
+                        cursor.getString(PDU_COLUMN));
+
+                // Read the destination port from the first segment (needed for CDMA WAP PDU).
+                // It's not a bad idea to prefer the port from the first segment for 3GPP as well.
+                if (cursorSequence == 0 && !cursor.isNull(DESTINATION_PORT_COLUMN)) {
+                    destPort = cursor.getInt(DESTINATION_PORT_COLUMN);
+                }
             }
             // This one isn't in the DB, so add it
-            pdus[concatRef.seqNumber - 1] = sms.getPdu();
+            pdus[sequenceNumber - 1] = pdu;
 
             // Remove the parts from the database
-            mResolver.delete(mRawUri, where.toString(), whereArgs);
+            mResolver.delete(mRawUri, where, whereArgs);
         } catch (SQLException e) {
             Log.e(TAG, "Can't access multipart SMS database", e);
-            // TODO:  Would OUT_OF_MEMORY be more appropriate?
             return Intents.RESULT_SMS_GENERIC_ERROR;
         } finally {
             if (cursor != null) cursor.close();
         }
 
-        /**
-         * TODO(cleanup): The following code has duplicated logic with
-         * the radio-specific dispatchMessage code, which is fragile,
-         * in addition to being redundant.  Instead, if this method
-         * maybe returned the reassembled message (or just contents),
-         * the following code (which is not really related to
-         * reconstruction) could be better consolidated.
-         */
+        // Special handling for CDMA WDP datagrams
+        if (isCdmaWapPush) {
+            // Build up the data stream
+            ByteArrayOutputStream output = new ByteArrayOutputStream();
+            for (int i = 0; i < messageCount; i++) {
+                // reassemble the (WSP-)pdu
+                output.write(pdus[i], 0, pdus[i].length);
+            }
+            byte[] datagram = output.toByteArray();
+
+            // Dispatch the PDU to applications
+            if (destPort == SmsHeader.PORT_WAP_PUSH) {
+                // Handle the PUSH
+                return mWapPush.dispatchWapPdu(datagram);
+            } else {
+                pdus = new byte[1][];
+                pdus[0] = datagram;
+                // The messages were sent to any other WAP port
+                dispatchPortAddressedPdus(pdus, destPort);
+                return Activity.RESULT_OK;
+            }
+        }
 
         // Dispatch the PDUs to applications
-        if (portAddrs != null) {
-            if (portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
+        if (destPort != -1) {
+            if (destPort == SmsHeader.PORT_WAP_PUSH) {
                 // Build up the data stream
                 ByteArrayOutputStream output = new ByteArrayOutputStream();
-                for (int i = 0; i < concatRef.msgCount; i++) {
-                    SmsMessage msg = SmsMessage.createFromPdu(pdus[i]);
+                for (int i = 0; i < messageCount; i++) {
+                    SmsMessage msg = SmsMessage.createFromPdu(pdus[i], getFormat());
                     byte[] data = msg.getUserData();
                     output.write(data, 0, data.length);
                 }
@@ -668,7 +641,7 @@
                 return mWapPush.dispatchWapPdu(output.toByteArray());
             } else {
                 // The messages were sent to a port, so concoct a URI for it
-                dispatchPortAddressedPdus(pdus, portAddrs.destPort);
+                dispatchPortAddressedPdus(pdus, destPort);
             }
         } else {
             // The messages were not sent to a port
@@ -685,7 +658,8 @@
     protected void dispatchPdus(byte[][] pdus) {
         Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
         intent.putExtra("pdus", pdus);
-        dispatch(intent, "android.permission.RECEIVE_SMS");
+        intent.putExtra("format", getFormat());
+        dispatch(intent, RECEIVE_SMS_PERMISSION);
     }
 
     /**
@@ -698,7 +672,8 @@
         Uri uri = Uri.parse("sms://localhost:" + port);
         Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
         intent.putExtra("pdus", pdus);
-        dispatch(intent, "android.permission.RECEIVE_SMS");
+        intent.putExtra("format", getFormat());
+        dispatch(intent, RECEIVE_SMS_PERMISSION);
     }
 
     /**
@@ -759,6 +734,16 @@
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent);
 
     /**
+     * Calculate the number of septets needed to encode the message.
+     *
+     * @param messageBody the message to encode
+     * @param use7bitOnly ignore (but still count) illegal characters if true
+     * @return TextEncodingDetails
+     */
+    protected abstract TextEncodingDetails calculateLength(CharSequence messageBody,
+            boolean use7bitOnly);
+
+    /**
      * Send a multi-part text based SMS.
      *
      * @param destAddr the address to send the message to
@@ -784,9 +769,70 @@
      *   to the recipient.  The raw pdu of the status report is in the
      *   extended data ("pdu").
      */
-    protected abstract void sendMultipartText(String destAddr, String scAddr,
+    protected void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
-            ArrayList<PendingIntent> deliveryIntents);
+            ArrayList<PendingIntent> deliveryIntents) {
+
+        int refNumber = getNextConcatenatedRef() & 0x00FF;
+        int msgCount = parts.size();
+        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
+
+        mRemainingMessages = msgCount;
+
+        TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
+        for (int i = 0; i < msgCount; i++) {
+            TextEncodingDetails details = calculateLength(parts.get(i), false);
+            if (encoding != details.codeUnitSize
+                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
+                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
+                encoding = details.codeUnitSize;
+            }
+            encodingForParts[i] = details;
+        }
+
+        for (int i = 0; i < msgCount; i++) {
+            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+            concatRef.refNumber = refNumber;
+            concatRef.seqNumber = i + 1;  // 1-based sequence
+            concatRef.msgCount = msgCount;
+            // TODO: We currently set this to true since our messaging app will never
+            // send more than 255 parts (it converts the message to MMS well before that).
+            // However, we should support 3rd party messaging apps that might need 16-bit
+            // references
+            // Note:  It's not sufficient to just flip this bit to true; it will have
+            // ripple effects (several calculations assume 8-bit ref).
+            concatRef.isEightBits = true;
+            SmsHeader smsHeader = new SmsHeader();
+            smsHeader.concatRef = concatRef;
+
+            // Set the national language tables for 3GPP 7-bit encoding, if enabled.
+            if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
+                smsHeader.languageTable = encodingForParts[i].languageTable;
+                smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
+            }
+
+            PendingIntent sentIntent = null;
+            if (sentIntents != null && sentIntents.size() > i) {
+                sentIntent = sentIntents.get(i);
+            }
+
+            PendingIntent deliveryIntent = null;
+            if (deliveryIntents != null && deliveryIntents.size() > i) {
+                deliveryIntent = deliveryIntents.get(i);
+            }
+
+            sendNewSubmitPdu(destAddr, scAddr, parts.get(i), smsHeader, encoding,
+                    sentIntent, deliveryIntent, (i == (msgCount - 1)));
+        }
+
+    }
+
+    /**
+     * Create a new SubmitPdu and send it.
+     */
+    protected abstract void sendNewSubmitPdu(String destinationAddress, String scAddress,
+            String message, SmsHeader smsHeader, int encoding,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart);
 
     /**
      * Send a SMS
@@ -842,7 +888,7 @@
             handleNotInService(ss, tracker);
         } else {
             String appName = getAppNameByIntent(sentIntent);
-            if (mCounter.check(appName, SINGLE_PART_SMS)) {
+            if (mUsageMonitor.check(appName, SINGLE_PART_SMS)) {
                 sendSms(tracker);
             } else {
                 sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
@@ -885,7 +931,7 @@
                 DEFAULT_SMS_TIMEOUT);
     }
 
-    protected String getAppNameByIntent(PendingIntent intent) {
+    protected static String getAppNameByIntent(PendingIntent intent) {
         Resources r = Resources.getSystem();
         return (intent != null) ? intent.getTargetPackage()
             : r.getString(R.string.sms_control_default_app_name);
@@ -903,7 +949,35 @@
      *
      * @param tracker holds the multipart Sms tracker ready to be sent
      */
-    protected abstract void sendMultipartSms (SmsTracker tracker);
+    private void sendMultipartSms(SmsTracker tracker) {
+        ArrayList<String> parts;
+        ArrayList<PendingIntent> sentIntents;
+        ArrayList<PendingIntent> deliveryIntents;
+
+        HashMap<String, Object> map = tracker.mData;
+
+        String destinationAddress = (String) map.get("destination");
+        String scAddress = (String) map.get("scaddress");
+
+        parts = (ArrayList<String>) map.get("parts");
+        sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents");
+        deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents");
+
+        // check if in service
+        int ss = mPhone.getServiceState().getState();
+        if (ss != ServiceState.STATE_IN_SERVICE) {
+            for (int i = 0, count = parts.size(); i < count; i++) {
+                PendingIntent sentIntent = null;
+                if (sentIntents != null && sentIntents.size() > i) {
+                    sentIntent = sentIntents.get(i);
+                }
+                handleNotInService(ss, new SmsTracker(null, sentIntent, null));
+            }
+            return;
+        }
+
+        sendMultipartText(destinationAddress, scAddress, parts, sentIntents, deliveryIntents);
+    }
 
     /**
      * Send an acknowledge message.
@@ -934,66 +1008,38 @@
     }
 
     /**
-     * Check if a SmsTracker holds multi-part Sms
-     *
-     * @param tracker a SmsTracker could hold a multi-part Sms
-     * @return true for tracker holds Multi-parts Sms
-     */
-    private boolean isMultipartTracker (SmsTracker tracker) {
-        HashMap map = tracker.mData;
-        return ( map.get("parts") != null);
-    }
-
-    /**
      * Keeps track of an SMS that has been sent to the RIL, until it has
      * successfully been sent, or we're done trying.
      *
      */
-    static protected class SmsTracker {
+    protected static final class SmsTracker {
         // fields need to be public for derived SmsDispatchers
-        public HashMap<String, Object> mData;
+        public final HashMap<String, Object> mData;
         public int mRetryCount;
         public int mMessageRef;
 
-        public PendingIntent mSentIntent;
-        public PendingIntent mDeliveryIntent;
+        public final PendingIntent mSentIntent;
+        public final PendingIntent mDeliveryIntent;
 
-        SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
+        public SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
                 PendingIntent deliveryIntent) {
             mData = data;
             mSentIntent = sentIntent;
             mDeliveryIntent = deliveryIntent;
             mRetryCount = 0;
         }
+
+        /**
+         * Returns whether this tracker holds a multi-part SMS.
+         * @return true if the tracker holds a multi-part SMS; false otherwise
+         */
+        protected boolean isMultipart() {
+            HashMap map = mData;
+            return map.containsKey("parts");
+        }
     }
 
-    protected SmsTracker SmsTrackerFactory(HashMap<String, Object> data, PendingIntent sentIntent,
-            PendingIntent deliveryIntent) {
-        return new SmsTracker(data, sentIntent, deliveryIntent);
-    }
-
-    public void initSipStack(boolean isObg) {
-        // This function should be overridden by the classes that support
-        // switching modes such as the CdmaSMSDispatcher.
-        // Not implemented in GsmSMSDispatcher.
-        Log.e(TAG, "Error! This function should never be executed.");
-    }
-
-    public void switchToCdma() {
-        // This function should be overridden by the classes that support
-        // switching modes such as the CdmaSMSDispatcher.
-        // Not implemented in GsmSMSDispatcher.
-        Log.e(TAG, "Error! This function should never be executed.");
-    }
-
-    public void switchToGsm() {
-        // This function should be overridden by the classes that support
-        // switching modes such as the CdmaSMSDispatcher.
-        // Not implemented in GsmSMSDispatcher.
-        Log.e(TAG, "Error! This function should never be executed.");
-    }
-
-    private DialogInterface.OnClickListener mListener =
+    private final DialogInterface.OnClickListener mListener =
         new DialogInterface.OnClickListener() {
 
             public void onClick(DialogInterface dialog, int which) {
@@ -1007,42 +1053,32 @@
             }
         };
 
-    private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) {
-                mStorageAvailable = false;
-                mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
-            } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) {
-                mStorageAvailable = true;
-                mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
-            } else {
-                // Assume the intent is one of the SMS receive intents that
-                // was sent as an ordered broadcast.  Check result and ACK.
-                int rc = getResultCode();
-                boolean success = (rc == Activity.RESULT_OK)
-                        || (rc == Intents.RESULT_SMS_HANDLED);
+            // Assume the intent is one of the SMS receive intents that
+            // was sent as an ordered broadcast.  Check result and ACK.
+            int rc = getResultCode();
+            boolean success = (rc == Activity.RESULT_OK)
+                    || (rc == Intents.RESULT_SMS_HANDLED);
 
-                // For a multi-part message, this only ACKs the last part.
-                // Previous parts were ACK'd as they were received.
-                acknowledgeLastIncomingSms(success, rc, null);
-            }
+            // For a multi-part message, this only ACKs the last part.
+            // Previous parts were ACK'd as they were received.
+            acknowledgeLastIncomingSms(success, rc, null);
         }
     };
 
-    protected abstract void handleBroadcastSms(AsyncResult ar);
-
     protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) {
         if (isEmergencyMessage) {
             Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
             intent.putExtra("pdus", pdus);
             Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus");
-            dispatch(intent, "android.permission.RECEIVE_EMERGENCY_BROADCAST");
+            dispatch(intent, RECEIVE_EMERGENCY_BROADCAST_PERMISSION);
         } else {
             Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
             intent.putExtra("pdus", pdus);
             Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
-            dispatch(intent, "android.permission.RECEIVE_SMS");
+            dispatch(intent, RECEIVE_SMS_PERMISSION);
         }
     }
 }
diff --git a/telephony/java/com/android/internal/telephony/SmsStorageMonitor.java b/telephony/java/com/android/internal/telephony/SmsStorageMonitor.java
new file mode 100644
index 0000000..0c06ffc
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsStorageMonitor.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.provider.Telephony.Sms.Intents;
+import android.util.Log;
+
+/**
+ * Monitors the device and ICC storage, and sends the appropriate events.
+ *
+ * This code was formerly part of {@link SMSDispatcher}, and has been moved
+ * into a separate class to support instantiation of multiple SMSDispatchers on
+ * dual-mode devices that require support for both 3GPP and 3GPP2 format messages.
+ */
+public final class SmsStorageMonitor extends Handler {
+    private static final String TAG = "SmsStorageMonitor";
+
+    /** SIM/RUIM storage is full */
+    private static final int EVENT_ICC_FULL = 1;
+
+    /** Memory status reporting is acknowledged by RIL */
+    private static final int EVENT_REPORT_MEMORY_STATUS_DONE = 2;
+
+    /** Radio is ON */
+    private static final int EVENT_RADIO_ON = 3;
+
+    /** Context from phone object passed to constructor. */
+    private final Context mContext;
+
+    /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
+    private PowerManager.WakeLock mWakeLock;
+
+    private boolean mReportMemoryStatusPending;
+
+    final CommandsInterface mCm;                            // accessed from inner class
+    boolean mStorageAvailable = true;                       // accessed from inner class
+
+    /**
+     * Hold the wake lock for 5 seconds, which should be enough time for
+     * any receiver(s) to grab its own wake lock.
+     */
+    private static final int WAKE_LOCK_TIMEOUT = 5000;
+
+    /**
+     * Creates an SmsStorageMonitor and registers for events.
+     * @param phone the Phone to use
+     */
+    public SmsStorageMonitor(PhoneBase phone) {
+        mContext = phone.getContext();
+        mCm = phone.mCM;
+
+        createWakelock();
+
+        mCm.setOnIccSmsFull(this, EVENT_ICC_FULL, null);
+        mCm.registerForOn(this, EVENT_RADIO_ON, null);
+
+        // Register for device storage intents.  Use these to notify the RIL
+        // that storage for SMS is or is not available.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_DEVICE_STORAGE_FULL);
+        filter.addAction(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
+        mContext.registerReceiver(mResultReceiver, filter);
+    }
+
+    public void dispose() {
+        mCm.unSetOnIccSmsFull(this);
+        mCm.unregisterForOn(this);
+        mContext.unregisterReceiver(mResultReceiver);
+    }
+
+    /**
+     * Handles events coming from the phone stack. Overridden from handler.
+     * @param msg the message to handle
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        AsyncResult ar;
+
+        switch (msg.what) {
+            case EVENT_ICC_FULL:
+                handleIccFull();
+                break;
+
+            case EVENT_REPORT_MEMORY_STATUS_DONE:
+                ar = (AsyncResult) msg.obj;
+                if (ar.exception != null) {
+                    mReportMemoryStatusPending = true;
+                    Log.v(TAG, "Memory status report to modem pending : mStorageAvailable = "
+                            + mStorageAvailable);
+                } else {
+                    mReportMemoryStatusPending = false;
+                }
+                break;
+
+            case EVENT_RADIO_ON:
+                if (mReportMemoryStatusPending) {
+                    Log.v(TAG, "Sending pending memory status report : mStorageAvailable = "
+                            + mStorageAvailable);
+                    mCm.reportSmsMemoryStatus(mStorageAvailable,
+                            obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+                }
+                break;
+        }
+    }
+
+    private void createWakelock() {
+        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SmsStorageMonitor");
+        mWakeLock.setReferenceCounted(true);
+    }
+
+    /**
+     * Called when SIM_FULL message is received from the RIL.  Notifies interested
+     * parties that SIM storage for SMS messages is full.
+     */
+    private void handleIccFull() {
+        // broadcast SIM_FULL intent
+        Intent intent = new Intent(Intents.SIM_FULL_ACTION);
+        mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
+        mContext.sendBroadcast(intent, SMSDispatcher.RECEIVE_SMS_PERMISSION);
+    }
+
+    /** Returns whether or not there is storage available for an incoming SMS. */
+    public boolean isStorageAvailable() {
+        return mStorageAvailable;
+    }
+
+    private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) {
+                mStorageAvailable = false;
+                mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+            } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) {
+                mStorageAvailable = true;
+                mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+            }
+        }
+    };
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java b/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java
new file mode 100644
index 0000000..bd2ae8b
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Implement the per-application based SMS control, which limits the number of
+ * SMS/MMS messages an app can send in the checking period.
+ *
+ * This code was formerly part of {@link SMSDispatcher}, and has been moved
+ * into a separate class to support instantiation of multiple SMSDispatchers on
+ * dual-mode devices that require support for both 3GPP and 3GPP2 format messages.
+ */
+public class SmsUsageMonitor {
+    private static final String TAG = "SmsStorageMonitor";
+
+    /** Default checking period for SMS sent without user permission. */
+    private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000;
+
+    /** Default number of SMS sent in checking period without user permission. */
+    private static final int DEFAULT_SMS_MAX_COUNT = 100;
+
+    private final int mCheckPeriod;
+    private final int mMaxAllowed;
+    private final HashMap<String, ArrayList<Long>> mSmsStamp =
+            new HashMap<String, ArrayList<Long>>();
+
+    /**
+     * Create SMS usage monitor.
+     * @param resolver the ContentResolver to use to load from secure settings
+     */
+    public SmsUsageMonitor(ContentResolver resolver) {
+        mMaxAllowed = Settings.Secure.getInt(resolver,
+                Settings.Secure.SMS_OUTGOING_CHECK_MAX_COUNT,
+                DEFAULT_SMS_MAX_COUNT);
+
+        mCheckPeriod = Settings.Secure.getInt(resolver,
+                Settings.Secure.SMS_OUTGOING_CHECK_INTERVAL_MS,
+                DEFAULT_SMS_CHECK_PERIOD);
+    }
+
+    /** Clear the SMS application list for disposal. */
+    void dispose() {
+        mSmsStamp.clear();
+    }
+
+    /**
+     * Check to see if an application is allowed to send new SMS messages.
+     *
+     * @param appName the application sending sms
+     * @param smsWaiting the number of new messages desired to send
+     * @return true if application is allowed to send the requested number
+     *  of new sms messages
+     */
+    public boolean check(String appName, int smsWaiting) {
+        synchronized (mSmsStamp) {
+            removeExpiredTimestamps();
+
+            ArrayList<Long> sentList = mSmsStamp.get(appName);
+            if (sentList == null) {
+                sentList = new ArrayList<Long>();
+                mSmsStamp.put(appName, sentList);
+            }
+
+            return isUnderLimit(sentList, smsWaiting);
+        }
+    }
+
+    /**
+     * Remove keys containing only old timestamps. This can happen if an SMS app is used
+     * to send messages and then uninstalled.
+     */
+    private void removeExpiredTimestamps() {
+        long beginCheckPeriod = System.currentTimeMillis() - mCheckPeriod;
+
+        synchronized (mSmsStamp) {
+            Iterator<Map.Entry<String, ArrayList<Long>>> iter = mSmsStamp.entrySet().iterator();
+            while (iter.hasNext()) {
+                Map.Entry<String, ArrayList<Long>> entry = iter.next();
+                ArrayList<Long> oldList = entry.getValue();
+                if (oldList.isEmpty() || oldList.get(oldList.size() - 1) < beginCheckPeriod) {
+                    iter.remove();
+                }
+            }
+        }
+    }
+
+    private boolean isUnderLimit(ArrayList<Long> sent, int smsWaiting) {
+        Long ct = System.currentTimeMillis();
+        long beginCheckPeriod = ct - mCheckPeriod;
+
+        Log.d(TAG, "SMS send size=" + sent.size() + " time=" + ct);
+
+        while (!sent.isEmpty() && sent.get(0) < beginCheckPeriod) {
+            sent.remove(0);
+        }
+
+        if ((sent.size() + smsWaiting) <= mMaxAllowed) {
+            for (int i = 0; i < smsWaiting; i++ ) {
+                sent.add(ct);
+            }
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
index 6903025..c2b9e4f 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
@@ -27,6 +27,9 @@
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.SMSDispatcher;
+import com.android.internal.telephony.gsm.GsmSMSDispatcher;
 import com.android.internal.telephony.gsm.SimCard;
 import com.android.internal.telephony.ims.IsimRecords;
 
@@ -35,14 +38,13 @@
 
     private static final boolean DBG = true;
 
+    /** Secondary SMSDispatcher for 3GPP format messages. */
+    SMSDispatcher m3gppSMS;
+
     // Constructors
     public CDMALTEPhone(Context context, CommandsInterface ci, PhoneNotifier notifier) {
-        this(context, ci, notifier, false);
-    }
-
-    public CDMALTEPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
-            boolean unitTestMode) {
         super(context, ci, notifier, false);
+        m3gppSMS = new GsmSMSDispatcher(this, mSmsStorageMonitor, mSmsUsageMonitor);
     }
 
     @Override
@@ -54,6 +56,20 @@
     }
 
     @Override
+    public void dispose() {
+        synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+            super.dispose();
+            m3gppSMS.dispose();
+        }
+    }
+
+    @Override
+    public void removeReferences() {
+        super.removeReferences();
+        m3gppSMS = null;
+    }
+
+    @Override
     public DataState getDataConnectionState(String apnType) {
         DataState ret = DataState.DISCONNECTED;
 
@@ -92,13 +108,15 @@
         return ret;
     }
 
+    @Override
     public boolean updateCurrentCarrierInProvider() {
         if (mIccRecords != null) {
             try {
                 Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
                 ContentValues map = new ContentValues();
-                map.put(Telephony.Carriers.NUMERIC, mIccRecords.getOperatorNumeric());
-                log("updateCurrentCarrierInProvider insert uri=" + uri);
+                String operatorNumeric = mIccRecords.getOperatorNumeric();
+                map.put(Telephony.Carriers.NUMERIC, operatorNumeric);
+                log("updateCurrentCarrierInProvider from UICC: numeric=" + operatorNumeric);
                 mContext.getContentResolver().insert(uri, map);
                 return true;
             } catch (SQLException e) {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 286515e..09ee28c 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -17,10 +17,9 @@
 package com.android.internal.telephony.cdma;
 
 import android.app.ActivityManagerNative;
-import android.content.Context;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.Intent;
-import android.content.res.Configuration;
 import android.content.SharedPreferences;
 import android.database.SQLException;
 import android.net.Uri;
@@ -31,7 +30,6 @@
 import android.os.PowerManager.WakeLock;
 import android.os.Registrant;
 import android.os.RegistrantList;
-import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.preference.PreferenceManager;
 import android.provider.Telephony;
@@ -42,20 +40,17 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.telephony.cat.CatService;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallTracker;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.DataConnection;
-import com.android.internal.telephony.IccRecords;
-import com.android.internal.telephony.MccTable;
-import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.IccException;
 import com.android.internal.telephony.IccFileHandler;
 import com.android.internal.telephony.IccPhoneBookInterfaceManager;
 import com.android.internal.telephony.IccSmsInterfaceManager;
+import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.MmiCode;
 import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.Phone;
@@ -67,19 +62,17 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.UUSInfo;
-import com.android.internal.telephony.CallTracker;
-
-import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
-import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
-import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
+import com.android.internal.telephony.cat.CatService;
 
 import java.util.ArrayList;
 import java.util.List;
-
-
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
+
 /**
  * {@hide}
  */
@@ -109,13 +102,13 @@
     CatService mCcatService;
 
     // mNvLoadedRegistrants are informed after the EVENT_NV_READY
-    private RegistrantList mNvLoadedRegistrants = new RegistrantList();
+    private final RegistrantList mNvLoadedRegistrants = new RegistrantList();
 
     // mEriFileLoadedRegistrants are informed after the ERI text has been loaded
-    private RegistrantList mEriFileLoadedRegistrants = new RegistrantList();
+    private final RegistrantList mEriFileLoadedRegistrants = new RegistrantList();
 
     // mEcmTimerResetRegistrants are informed after Ecm timer is canceled or re-started
-    private RegistrantList mEcmTimerResetRegistrants = new RegistrantList();
+    private final RegistrantList mEcmTimerResetRegistrants = new RegistrantList();
 
     // mEcmExitRespRegistrant is informed after the phone has been exited
     //the emergency callback mode
@@ -131,6 +124,7 @@
 
     // A runnable which is used to automatically exit from Ecm after a period of time.
     private Runnable mExitEcmRunnable = new Runnable() {
+        @Override
         public void run() {
             exitEmergencyCallbackMode();
         }
@@ -164,7 +158,7 @@
     protected void init(Context context, PhoneNotifier notifier) {
         mCM.setPhoneType(Phone.PHONE_TYPE_CDMA);
         mCT = new CdmaCallTracker(this);
-        mSMS = new CdmaSMSDispatcher(this);
+        mSMS = new CdmaSMSDispatcher(this, mSmsStorageMonitor, mSmsUsageMonitor);
         mDataConnectionTracker = new CdmaDataConnectionTracker (this);
         mRuimPhoneBookInterfaceManager = new RuimPhoneBookInterfaceManager(this);
         mRuimSmsInterfaceManager = new RuimSmsInterfaceManager(this, mSMS);
@@ -188,7 +182,7 @@
 
         //Change the system setting
         SystemProperties.set(TelephonyProperties.CURRENT_ACTIVE_PHONE,
-                new Integer(Phone.PHONE_TYPE_CDMA).toString());
+                Integer.toString(Phone.PHONE_TYPE_CDMA));
 
         // This is needed to handle phone process crashes
         String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
@@ -220,6 +214,7 @@
         notifier.notifyMessageWaitingChanged(this);
     }
 
+    @Override
     public void dispose() {
         synchronized(PhoneProxy.lockForRadioTechnologyChange) {
             super.dispose();
@@ -253,23 +248,26 @@
         }
     }
 
+    @Override
     public void removeReferences() {
-            log("removeReferences");
-            this.mRuimPhoneBookInterfaceManager = null;
-            this.mRuimSmsInterfaceManager = null;
-            this.mSMS = null;
-            this.mSubInfo = null;
-            this.mIccRecords = null;
-            this.mIccFileHandler = null;
-            this.mIccCard = null;
-            this.mDataConnectionTracker = null;
-            this.mCT = null;
-            this.mSST = null;
-            this.mEriManager = null;
-            this.mCcatService = null;
-            this.mExitEcmRunnable = null;
+        log("removeReferences");
+        super.removeReferences();
+        mRuimPhoneBookInterfaceManager = null;
+        mRuimSmsInterfaceManager = null;
+        mSMS = null;
+        mSubInfo = null;
+        mIccRecords = null;
+        mIccFileHandler = null;
+        mIccCard = null;
+        mDataConnectionTracker = null;
+        mCT = null;
+        mSST = null;
+        mEriManager = null;
+        mCcatService = null;
+        mExitEcmRunnable = null;
     }
 
+    @Override
     protected void finalize() {
         if(DBG) Log.d(LOG_TAG, "CDMAPhone finalized");
         if (mWakeLock.isHeld()) {
@@ -813,7 +811,7 @@
         return null;
     }
 
-   /**
+    /**
      * Notify any interested party of a Phone state change  {@link Phone.State}
      */
     /*package*/ void notifyPhoneStateChanged() {
@@ -858,18 +856,6 @@
         if (DBG) Log.d(LOG_TAG, "sendEmergencyCallbackModeChange");
     }
 
-    /*package*/ void
-    updateMessageWaitingIndicator(boolean mwi) {
-        // this also calls notifyMessageWaitingIndicator()
-        mIccRecords.setVoiceMessageWaiting(1, mwi ? -1 : 0);
-    }
-
-    /* This function is overloaded to send number of voicemails instead of sending true/false */
-    /*package*/ void
-    updateMessageWaitingIndicator(int mwi) {
-        mIccRecords.setVoiceMessageWaiting(1, mwi);
-    }
-
     @Override
     public void exitEmergencyCallbackMode() {
         if (mWakeLock.isHeld()) {
@@ -1013,6 +999,7 @@
 
             case EVENT_RUIM_RECORDS_LOADED:{
                 Log.d(LOG_TAG, "Event EVENT_RUIM_RECORDS_LOADED Received");
+                updateCurrentCarrierInProvider();
             }
             break;
 
@@ -1172,7 +1159,7 @@
     private static final int IS683_CONST_1900MHZ_F_BLOCK = 7;
     private static final int INVALID_SYSTEM_SELECTION_CODE = -1;
 
-    private boolean isIs683OtaSpDialStr(String dialStr) {
+    private static boolean isIs683OtaSpDialStr(String dialStr) {
         int sysSelCodeInt;
         boolean isOtaspDialString = false;
         int dialStrLen = dialStr.length();
@@ -1203,7 +1190,7 @@
     /**
      * This function extracts the system selection code from the dial string.
      */
-    private int extractSelCodeFromOtaSpNum(String dialStr) {
+    private static int extractSelCodeFromOtaSpNum(String dialStr) {
         int dialStrLen = dialStr.length();
         int sysSelCodeInt = INVALID_SYSTEM_SELECTION_CODE;
 
@@ -1226,7 +1213,7 @@
      * the dial string "sysSelCodeInt' is the system selection code specified
      * in the carrier ota sp number schema "sch".
      */
-    private boolean
+    private static boolean
     checkOtaSpNumBasedOnSysSelCode (int sysSelCodeInt, String sch[]) {
         boolean isOtaSpNum = false;
         try {
@@ -1414,7 +1401,7 @@
                 Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
                 ContentValues map = new ContentValues();
                 map.put(Telephony.Carriers.NUMERIC, operatorNumeric);
-                log("updateCurrentCarrierInProvider insert uri=" + uri);
+                log("updateCurrentCarrierInProvider from system: numeric=" + operatorNumeric);
                 getContext().getContentResolver().insert(uri, map);
 
                 // Updates MCC MNC device configuration information
@@ -1428,6 +1415,16 @@
         return false;
     }
 
+    /**
+     * Sets the "current" field in the telephony provider according to the SIM's operator.
+     * Implemented in {@link CDMALTEPhone} for CDMA/LTE devices.
+     *
+     * @return true for success; false otherwise.
+     */
+    boolean updateCurrentCarrierInProvider() {
+        return true;
+    }
+
     public void prepareEri() {
         mEriManager.loadEriFile();
         if(mEriManager.isEriFileLoaded()) {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
index 0617fee..47c638f 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
@@ -26,6 +26,7 @@
 import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.telephony.gsm.SIMRecords;
 import com.android.internal.telephony.ims.IsimRecords;
@@ -438,4 +439,13 @@
         }
         return true;
     }
+
+    /**
+     * Dispatch 3GPP format message. For CDMA/LTE phones,
+     * send the message to the secondary 3GPP format SMS dispatcher.
+     */
+    @Override
+    protected int dispatchGsmMessage(SmsMessageBase message) {
+        return ((CDMALTEPhone) phone).m3gppSMS.dispatchMessage(message);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index 07b0f4f..dded39e 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -25,7 +25,6 @@
 import android.content.SharedPreferences;
 import android.database.Cursor;
 import android.database.SQLException;
-import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemProperties;
 import android.preference.PreferenceManager;
@@ -40,6 +39,8 @@
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
+import com.android.internal.telephony.SmsStorageMonitor;
+import com.android.internal.telephony.SmsUsageMonitor;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.WspTypeDecoder;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
@@ -47,7 +48,6 @@
 import com.android.internal.util.HexDump;
 
 import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 
@@ -60,24 +60,23 @@
     private byte[] mLastDispatchedSmsFingerprint;
     private byte[] mLastAcknowledgedSmsFingerprint;
 
-    private boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
+    private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
             com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
 
-    CdmaSMSDispatcher(CDMAPhone phone) {
-        super(phone);
+    CdmaSMSDispatcher(CDMAPhone phone, SmsStorageMonitor storageMonitor,
+            SmsUsageMonitor usageMonitor) {
+        super(phone, storageMonitor, usageMonitor);
+        mCm.setOnNewCdmaSms(this, EVENT_NEW_SMS, null);
     }
 
-    /**
-     * Called when a status report is received.  This should correspond to
-     * a previously successful SEND.
-     * Is a special GSM function, should never be called in CDMA!!
-     *
-     * @param ar AsyncResult passed into the message handler.  ar.result should
-     *           be a String representing the status report PDU, as ASCII hex.
-     */
     @Override
-    protected void handleStatusReport(AsyncResult ar) {
-        Log.d(TAG, "handleStatusReport is a special GSM function, should never be called in CDMA!");
+    public void dispose() {
+        mCm.unSetOnNewCdmaSms(this);
+    }
+
+    @Override
+    protected String getFormat() {
+        return android.telephony.SmsMessage.FORMAT_3GPP2;
     }
 
     private void handleCdmaStatusReport(SmsMessage sms) {
@@ -138,11 +137,11 @@
             Log.d(TAG, "Voicemail count=" + voicemailCount);
             // Store the voicemail count in preferences.
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
-                    mPhone.getContext());
+                    mContext);
             SharedPreferences.Editor editor = sp.edit();
             editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
             editor.apply();
-            ((CDMAPhone) mPhone).updateMessageWaitingIndicator(voicemailCount);
+            mPhone.setVoiceMessageWaiting(1, voicemailCount);
             handled = true;
         } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) ||
                 (SmsEnvelope.TELESERVICE_WEMT == teleService)) &&
@@ -160,7 +159,8 @@
             return Intents.RESULT_SMS_HANDLED;
         }
 
-        if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) {
+        if (!mStorageMonitor.isStorageAvailable() &&
+                sms.getMessageClass() != MessageClass.CLASS_0) {
             // It's a storable message and there's no storage available.  Bail.
             // (See C.S0015-B v2.0 for a description of "Immediate Display"
             // messages, which we represent as CLASS_0.)
@@ -181,48 +181,7 @@
             return Intents.RESULT_SMS_UNSUPPORTED;
         }
 
-        /*
-         * TODO(cleanup): Why are we using a getter method for this
-         * (and for so many other sms fields)?  Trivial getters and
-         * setters like this are direct violations of the style guide.
-         * If the purpose is to protect against writes (by not
-         * providing a setter) then any protection is illusory (and
-         * hence bad) for cases where the values are not primitives,
-         * such as this call for the header.  Since this is an issue
-         * with the public API it cannot be changed easily, but maybe
-         * something can be done eventually.
-         */
-        SmsHeader smsHeader = sms.getUserDataHeader();
-
-        /*
-         * TODO(cleanup): Since both CDMA and GSM use the same header
-         * format, this dispatch processing is naturally identical,
-         * and code should probably not be replicated explicitly.
-         */
-
-        // See if message is partial or port addressed.
-        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
-            // Message is not partial (not part of concatenated sequence).
-            byte[][] pdus = new byte[1][];
-            pdus[0] = sms.getPdu();
-
-            if (smsHeader != null && smsHeader.portAddrs != null) {
-                if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
-                    // GSM-style WAP indication
-                    return mWapPush.dispatchWapPdu(sms.getUserData());
-                } else {
-                    // The message was sent to a port, so concoct a URI for it.
-                    dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
-                }
-            } else {
-                // Normal short and non-port-addressed message, dispatch it.
-                dispatchPdus(pdus);
-            }
-            return Activity.RESULT_OK;
-        } else {
-            // Process the message part.
-            return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
-        }
+        return dispatchNormalMessage(smsb);
     }
 
     /**
@@ -236,23 +195,19 @@
      *         to applications
      */
     protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) {
-        int segment;
-        int totalSegments;
         int index = 0;
-        int msgType;
 
-        int sourcePort = 0;
-        int destinationPort = 0;
-
-        msgType = pdu[index++];
-        if (msgType != 0){
+        int msgType = pdu[index++];
+        if (msgType != 0) {
             Log.w(TAG, "Received a WAP SMS which is not WDP. Discard.");
             return Intents.RESULT_SMS_HANDLED;
         }
-        totalSegments = pdu[index++]; // >=1
-        segment = pdu[index++]; // >=0
+        int totalSegments = pdu[index++];   // >= 1
+        int segment = pdu[index++];         // >= 0
 
         // Only the first segment contains sourcePort and destination Port
+        int sourcePort = 0;
+        int destinationPort = 0;
         if (segment == 0) {
             //process WDP segment
             sourcePort = (0xFF & pdu[index++]) << 8;
@@ -269,90 +224,16 @@
         }
 
         // Lookup all other related parts
-        StringBuilder where = new StringBuilder("reference_number =");
-        where.append(referenceNumber);
-        where.append(" AND address = ?");
-        String[] whereArgs = new String[] {address};
-
         Log.i(TAG, "Received WAP PDU. Type = " + msgType + ", originator = " + address
                 + ", src-port = " + sourcePort + ", dst-port = " + destinationPort
-                + ", ID = " + referenceNumber + ", segment# = " + segment + "/" + totalSegments);
+                + ", ID = " + referenceNumber + ", segment# = " + segment + '/' + totalSegments);
 
-        byte[][] pdus = null;
-        Cursor cursor = null;
-        try {
-            cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
-            int cursorCount = cursor.getCount();
-            if (cursorCount != totalSegments - 1) {
-                // We don't have all the parts yet, store this one away
-                ContentValues values = new ContentValues();
-                values.put("date", (long) 0);
-                values.put("pdu", HexDump.toHexString(pdu, index, pdu.length - index));
-                values.put("address", address);
-                values.put("reference_number", referenceNumber);
-                values.put("count", totalSegments);
-                values.put("sequence", segment);
-                values.put("destination_port", destinationPort);
+        // pass the user data portion of the PDU to the shared handler in SMSDispatcher
+        byte[] userData = new byte[pdu.length - index];
+        System.arraycopy(pdu, index, userData, 0, pdu.length - index);
 
-                mResolver.insert(mRawUri, values);
-
-                return Intents.RESULT_SMS_HANDLED;
-            }
-
-            // All the parts are in place, deal with them
-            int pduColumn = cursor.getColumnIndex("pdu");
-            int sequenceColumn = cursor.getColumnIndex("sequence");
-
-            pdus = new byte[totalSegments][];
-            for (int i = 0; i < cursorCount; i++) {
-                cursor.moveToNext();
-                int cursorSequence = (int)cursor.getLong(sequenceColumn);
-                // Read the destination port from the first segment
-                if (cursorSequence == 0) {
-                    int destinationPortColumn = cursor.getColumnIndex("destination_port");
-                    destinationPort = (int)cursor.getLong(destinationPortColumn);
-                }
-                pdus[cursorSequence] = HexDump.hexStringToByteArray(
-                        cursor.getString(pduColumn));
-            }
-            // The last part will be added later
-
-            // Remove the parts from the database
-            mResolver.delete(mRawUri, where.toString(), whereArgs);
-        } catch (SQLException e) {
-            Log.e(TAG, "Can't access multipart SMS database", e);
-            return Intents.RESULT_SMS_GENERIC_ERROR;
-        } finally {
-            if (cursor != null) cursor.close();
-        }
-
-        // Build up the data stream
-        ByteArrayOutputStream output = new ByteArrayOutputStream();
-        for (int i = 0; i < totalSegments; i++) {
-            // reassemble the (WSP-)pdu
-            if (i == segment) {
-                // This one isn't in the DB, so add it
-                output.write(pdu, index, pdu.length - index);
-            } else {
-                output.write(pdus[i], 0, pdus[i].length);
-            }
-        }
-
-        byte[] datagram = output.toByteArray();
-        // Dispatch the PDU to applications
-        switch (destinationPort) {
-        case SmsHeader.PORT_WAP_PUSH:
-            // Handle the PUSH
-            return mWapPush.dispatchWapPdu(datagram);
-
-        default:{
-            pdus = new byte[1][];
-            pdus[0] = datagram;
-            // The messages were sent to any other WAP port
-            dispatchPortAddressedPdus(pdus, destinationPort);
-            return Activity.RESULT_OK;
-        }
-        }
+        return processMessagePart(userData, address, referenceNumber, segment, totalSegments,
+                0L, destinationPort, true);
     }
 
     /** {@inheritDoc} */
@@ -375,68 +256,34 @@
 
     /** {@inheritDoc} */
     @Override
-    protected void sendMultipartText(String destAddr, String scAddr,
-            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
-            ArrayList<PendingIntent> deliveryIntents) {
+    protected TextEncodingDetails calculateLength(CharSequence messageBody,
+            boolean use7bitOnly) {
+        return SmsMessage.calculateLength(messageBody, use7bitOnly);
+    }
 
-        /**
-         * TODO(cleanup): There is no real code difference between
-         * this and the GSM version, and hence it should be moved to
-         * the base class or consolidated somehow, provided calling
-         * the proper submit pdu stuff can be arranged.
-         */
-
-        int refNumber = getNextConcatenatedRef() & 0x00FF;
-        int msgCount = parts.size();
-        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
-
-        for (int i = 0; i < msgCount; i++) {
-            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
-            if (encoding != details.codeUnitSize
-                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
-                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
-                encoding = details.codeUnitSize;
-            }
+    /** {@inheritDoc} */
+    @Override
+    protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
+            String message, SmsHeader smsHeader, int encoding,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
+        UserData uData = new UserData();
+        uData.payloadStr = message;
+        uData.userDataHeader = smsHeader;
+        if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
+            uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+        } else { // assume UTF-16
+            uData.msgEncoding = UserData.ENCODING_UNICODE_16;
         }
+        uData.msgEncodingSet = true;
 
-        for (int i = 0; i < msgCount; i++) {
-            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
-            concatRef.refNumber = refNumber;
-            concatRef.seqNumber = i + 1;  // 1-based sequence
-            concatRef.msgCount = msgCount;
-            concatRef.isEightBits = true;
-            SmsHeader smsHeader = new SmsHeader();
-            smsHeader.concatRef = concatRef;
+        /* By setting the statusReportRequested bit only for the
+         * last message fragment, this will result in only one
+         * callback to the sender when that last fragment delivery
+         * has been acknowledged. */
+        SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
+                uData, (deliveryIntent != null) && lastPart);
 
-            PendingIntent sentIntent = null;
-            if (sentIntents != null && sentIntents.size() > i) {
-                sentIntent = sentIntents.get(i);
-            }
-
-            PendingIntent deliveryIntent = null;
-            if (deliveryIntents != null && deliveryIntents.size() > i) {
-                deliveryIntent = deliveryIntents.get(i);
-            }
-
-            UserData uData = new UserData();
-            uData.payloadStr = parts.get(i);
-            uData.userDataHeader = smsHeader;
-            if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
-                uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
-            } else { // assume UTF-16
-                uData.msgEncoding = UserData.ENCODING_UNICODE_16;
-            }
-            uData.msgEncodingSet = true;
-
-            /* By setting the statusReportRequested bit only for the
-             * last message fragment, this will result in only one
-             * callback to the sender when that last fragment delivery
-             * has been acknowledged. */
-            SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destAddr,
-                    uData, (deliveryIntent != null) && (i == (msgCount - 1)));
-
-            sendSubmitPdu(submitPdu, sentIntent, deliveryIntent);
-        }
+        sendSubmitPdu(submitPdu, sentIntent, deliveryIntent);
     }
 
     protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
@@ -464,43 +311,27 @@
         byte pdu[] = (byte[]) map.get("pdu");
 
         Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
-
         mCm.sendCdmaSms(pdu, reply);
     }
 
-     /** {@inheritDoc} */
-    @Override
-    protected void sendMultipartSms (SmsTracker tracker) {
-        Log.d(TAG, "TODO: CdmaSMSDispatcher.sendMultipartSms not implemented");
-    }
-
     /** {@inheritDoc} */
     @Override
-    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
-        // FIXME unit test leaves cm == null. this should change
-
+    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
         String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
         if (inEcm.equals("true")) {
             return;
         }
 
-        if (mCm != null) {
-            int causeCode = resultToCause(result);
-            mCm.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
+        int causeCode = resultToCause(result);
+        mCm.acknowledgeLastIncomingCdmaSms(success, causeCode, response);
 
-            if (causeCode == 0) {
-                mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
-            }
-            mLastDispatchedSmsFingerprint = null;
+        if (causeCode == 0) {
+            mLastAcknowledgedSmsFingerprint = mLastDispatchedSmsFingerprint;
         }
+        mLastDispatchedSmsFingerprint = null;
     }
 
-    protected void handleBroadcastSms(AsyncResult ar) {
-        // Not supported
-        Log.e(TAG, "Error! Not implemented for CDMA.");
-    }
-
-    private int resultToCause(int rc) {
+    private static int resultToCause(int rc) {
         switch (rc) {
         case Activity.RESULT_OK:
         case Intents.RESULT_SMS_HANDLED:
@@ -527,7 +358,7 @@
      * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
      *         False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
      */
-    private boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) {
+    private static boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) {
         index += 4;
         byte[] omaPdu = new byte[origPdu.length - index];
         System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index be5c616..1409cab 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -114,30 +114,6 @@
     }
 
     /**
-     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
-     */
-    public static SmsMessage newFromCMT(String[] lines) {
-        Log.w(LOG_TAG, "newFromCMT: is not supported in CDMA mode.");
-        return null;
-    }
-
-    /**
-     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
-     */
-    public static SmsMessage newFromCMTI(String line) {
-        Log.w(LOG_TAG, "newFromCMTI: is not supported in CDMA mode.");
-        return null;
-    }
-
-    /**
-     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
-     */
-    public static SmsMessage newFromCDS(String line) {
-        Log.w(LOG_TAG, "newFromCDS: is not supported in CDMA mode.");
-        return null;
-    }
-
-    /**
      *  Create a "raw" CDMA SmsMessage from a Parcel that was forged in ril.cpp.
      *  Note: Only primitive fields are set.
      */
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index d325aaa..e1f4c4b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -56,9 +56,6 @@
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.DataConnection;
-import com.android.internal.telephony.DataConnectionTracker;
-import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.IccFileHandler;
 import com.android.internal.telephony.IccPhoneBookInterfaceManager;
 import com.android.internal.telephony.IccSmsInterfaceManager;
@@ -140,7 +137,7 @@
         mCM.setPhoneType(Phone.PHONE_TYPE_GSM);
         mCT = new GsmCallTracker(this);
         mSST = new GsmServiceStateTracker (this);
-        mSMS = new GsmSMSDispatcher(this);
+        mSMS = new GsmSMSDispatcher(this, mSmsStorageMonitor, mSmsUsageMonitor);
         mIccFileHandler = new SIMFileHandler(this);
         mIccRecords = new SIMRecords(this);
         mDataConnectionTracker = new GsmDataConnectionTracker (this);
@@ -199,6 +196,7 @@
                 new Integer(Phone.PHONE_TYPE_GSM).toString());
     }
 
+    @Override
     public void dispose() {
         synchronized(PhoneProxy.lockForRadioTechnologyChange) {
             super.dispose();
@@ -228,19 +226,22 @@
         }
     }
 
+    @Override
     public void removeReferences() {
-            this.mSimulatedRadioControl = null;
-            this.mStkService = null;
-            this.mSimPhoneBookIntManager = null;
-            this.mSimSmsIntManager = null;
-            this.mSMS = null;
-            this.mSubInfo = null;
-            this.mIccRecords = null;
-            this.mIccFileHandler = null;
-            this.mIccCard = null;
-            this.mDataConnectionTracker = null;
-            this.mCT = null;
-            this.mSST = null;
+        Log.d(LOG_TAG, "removeReferences");
+        super.removeReferences();
+        mSimulatedRadioControl = null;
+        mStkService = null;
+        mSimPhoneBookIntManager = null;
+        mSimSmsIntManager = null;
+        mSMS = null;
+        mSubInfo = null;
+        mIccRecords = null;
+        mIccFileHandler = null;
+        mIccCard = null;
+        mDataConnectionTracker = null;
+        mCT = null;
+        mSST = null;
     }
 
     protected void finalize() {
@@ -406,17 +407,6 @@
     }
 
     public void
-    notifyDataConnectionFailed(String reason, String apnType) {
-        mNotifier.notifyDataConnectionFailed(this, reason, apnType);
-    }
-
-    /*package*/ void
-    updateMessageWaitingIndicator(boolean mwi) {
-        // this also calls notifyMessageWaitingIndicator()
-        mIccRecords.setVoiceMessageWaiting(1, mwi ? -1 : 0);
-    }
-
-    public void
     notifyCallForwardingIndicator() {
         mNotifier.notifyCallForwardingChanged(this);
     }
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 52ca453..4e1cc9a 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -30,13 +30,15 @@
 import android.telephony.gsm.GsmCellLocation;
 import android.util.Log;
 
-import com.android.internal.telephony.BaseCommands;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.PhoneBase;
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
+import com.android.internal.telephony.SmsStorageMonitor;
+import com.android.internal.telephony.SmsUsageMonitor;
 import com.android.internal.telephony.TelephonyProperties;
 
 import java.util.ArrayList;
@@ -45,16 +47,55 @@
 
 import static android.telephony.SmsMessage.MessageClass;
 
-final class GsmSMSDispatcher extends SMSDispatcher {
+public final class GsmSMSDispatcher extends SMSDispatcher {
     private static final String TAG = "GSM";
 
-    private GSMPhone mGsmPhone;
+    /** Status report received */
+    private static final int EVENT_NEW_SMS_STATUS_REPORT = 100;
 
-    GsmSMSDispatcher(GSMPhone phone) {
-        super(phone);
-        mGsmPhone = phone;
+    /** New broadcast SMS */
+    private static final int EVENT_NEW_BROADCAST_SMS = 101;
 
-        ((BaseCommands)mCm).setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null);
+    public GsmSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
+            SmsUsageMonitor usageMonitor) {
+        super(phone, storageMonitor, usageMonitor);
+        mCm.setOnNewGsmSms(this, EVENT_NEW_SMS, null);
+        mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
+        mCm.setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null);
+    }
+
+    @Override
+    public void dispose() {
+        mCm.unSetOnNewGsmSms(this);
+        mCm.unSetOnSmsStatus(this);
+        mCm.unSetOnNewGsmBroadcastSms(this);
+    }
+
+    @Override
+    protected String getFormat() {
+        return android.telephony.SmsMessage.FORMAT_3GPP;
+    }
+
+    /**
+     * Handles 3GPP format-specific events coming from the phone stack.
+     * Other events are handled by {@link SMSDispatcher#handleMessage}.
+     *
+     * @param msg the message to handle
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+        case EVENT_NEW_SMS_STATUS_REPORT:
+            handleStatusReport((AsyncResult) msg.obj);
+            break;
+
+        case EVENT_NEW_BROADCAST_SMS:
+            handleBroadcastSms((AsyncResult)msg.obj);
+            break;
+
+        default:
+            super.handleMessage(msg);
+        }
     }
 
     /**
@@ -64,8 +105,7 @@
      * @param ar AsyncResult passed into the message handler.  ar.result should
      *           be a String representing the status report PDU, as ASCII hex.
      */
-    @Override
-    protected void handleStatusReport(AsyncResult ar) {
+    private void handleStatusReport(AsyncResult ar) {
         String pduString = (String) ar.result;
         SmsMessage sms = SmsMessage.newFromCDS(pduString);
 
@@ -94,17 +134,17 @@
         acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null);
     }
 
-
     /** {@inheritDoc} */
     @Override
     public int dispatchMessage(SmsMessageBase smsb) {
 
         // If sms is null, means there was a parsing error.
         if (smsb == null) {
+            Log.e(TAG, "dispatchMessage: message is null");
             return Intents.RESULT_SMS_GENERIC_ERROR;
         }
+
         SmsMessage sms = (SmsMessage) smsb;
-        boolean handled = false;
 
         if (sms.isTypeZero()) {
             // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
@@ -121,14 +161,15 @@
         }
 
         // Special case the message waiting indicator messages
+        boolean handled = false;
         if (sms.isMWISetMessage()) {
-            mGsmPhone.updateMessageWaitingIndicator(true);
+            mPhone.setVoiceMessageWaiting(1, -1);  // line 1: unknown number of msgs waiting
             handled = sms.isMwiDontStore();
             if (false) {
                 Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
             }
         } else if (sms.isMWIClearMessage()) {
-            mGsmPhone.updateMessageWaitingIndicator(false);
+            mPhone.setVoiceMessageWaiting(1, 0);   // line 1: no msgs waiting
             handled = sms.isMwiDontStore();
             if (false) {
                 Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
@@ -139,35 +180,14 @@
             return Intents.RESULT_SMS_HANDLED;
         }
 
-        if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) {
+        if (!mStorageMonitor.isStorageAvailable() &&
+                sms.getMessageClass() != MessageClass.CLASS_0) {
             // It's a storable message and there's no storage available.  Bail.
             // (See TS 23.038 for a description of class 0 messages.)
             return Intents.RESULT_SMS_OUT_OF_MEMORY;
         }
 
-        SmsHeader smsHeader = sms.getUserDataHeader();
-         // See if message is partial or port addressed.
-        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
-            // Message is not partial (not part of concatenated sequence).
-            byte[][] pdus = new byte[1][];
-            pdus[0] = sms.getPdu();
-
-            if (smsHeader != null && smsHeader.portAddrs != null) {
-                if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
-                    return mWapPush.dispatchWapPdu(sms.getUserData());
-                } else {
-                    // The message was sent to a port, so concoct a URI for it.
-                    dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
-                }
-            } else {
-                // Normal short and non-port-addressed message, dispatch it.
-                dispatchPdus(pdus);
-            }
-            return Activity.RESULT_OK;
-        } else {
-            // Process the message part.
-            return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
-        }
+        return dispatchNormalMessage(smsb);
     }
 
     /** {@inheritDoc} */
@@ -190,158 +210,20 @@
 
     /** {@inheritDoc} */
     @Override
-    protected void sendMultipartText(String destinationAddress, String scAddress,
-            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
-            ArrayList<PendingIntent> deliveryIntents) {
-
-        int refNumber = getNextConcatenatedRef() & 0x00FF;
-        int msgCount = parts.size();
-        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
-
-        mRemainingMessages = msgCount;
-
-        TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
-        for (int i = 0; i < msgCount; i++) {
-            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
-            if (encoding != details.codeUnitSize
-                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
-                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
-                encoding = details.codeUnitSize;
-            }
-            encodingForParts[i] = details;
-        }
-
-        for (int i = 0; i < msgCount; i++) {
-            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
-            concatRef.refNumber = refNumber;
-            concatRef.seqNumber = i + 1;  // 1-based sequence
-            concatRef.msgCount = msgCount;
-            // TODO: We currently set this to true since our messaging app will never
-            // send more than 255 parts (it converts the message to MMS well before that).
-            // However, we should support 3rd party messaging apps that might need 16-bit
-            // references
-            // Note:  It's not sufficient to just flip this bit to true; it will have
-            // ripple effects (several calculations assume 8-bit ref).
-            concatRef.isEightBits = true;
-            SmsHeader smsHeader = new SmsHeader();
-            smsHeader.concatRef = concatRef;
-            if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
-                smsHeader.languageTable = encodingForParts[i].languageTable;
-                smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
-            }
-
-            PendingIntent sentIntent = null;
-            if (sentIntents != null && sentIntents.size() > i) {
-                sentIntent = sentIntents.get(i);
-            }
-
-            PendingIntent deliveryIntent = null;
-            if (deliveryIntents != null && deliveryIntents.size() > i) {
-                deliveryIntent = deliveryIntents.get(i);
-            }
-
-            SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
-                    parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
-                    encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
-
-            sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
-        }
+    protected TextEncodingDetails calculateLength(CharSequence messageBody,
+            boolean use7bitOnly) {
+        return SmsMessage.calculateLength(messageBody, use7bitOnly);
     }
 
-    /**
-     * Send a multi-part text based SMS which already passed SMS control check.
-     *
-     * It is the working function for sendMultipartText().
-     *
-     * @param destinationAddress the address to send the message to
-     * @param scAddress is the service center address or null to use
-     *   the current default SMSC
-     * @param parts an <code>ArrayList</code> of strings that, in order,
-     *   comprise the original message
-     * @param sentIntents if not null, an <code>ArrayList</code> of
-     *   <code>PendingIntent</code>s (one for each message part) that is
-     *   broadcast when the corresponding message part has been sent.
-     *   The result code will be <code>Activity.RESULT_OK<code> for success,
-     *   or one of these errors:
-     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
-     *   <code>RESULT_ERROR_RADIO_OFF</code>
-     *   <code>RESULT_ERROR_NULL_PDU</code>.
-     * @param deliveryIntents if not null, an <code>ArrayList</code> of
-     *   <code>PendingIntent</code>s (one for each message part) that is
-     *   broadcast when the corresponding message part has been delivered
-     *   to the recipient.  The raw pdu of the status report is in the
-     *   extended data ("pdu").
-     */
-    private void sendMultipartTextWithPermit(String destinationAddress,
-            String scAddress, ArrayList<String> parts,
-            ArrayList<PendingIntent> sentIntents,
-            ArrayList<PendingIntent> deliveryIntents) {
-
-        // check if in service
-        int ss = mPhone.getServiceState().getState();
-        if (ss != ServiceState.STATE_IN_SERVICE) {
-            for (int i = 0, count = parts.size(); i < count; i++) {
-                PendingIntent sentIntent = null;
-                if (sentIntents != null && sentIntents.size() > i) {
-                    sentIntent = sentIntents.get(i);
-                }
-                SmsTracker tracker = SmsTrackerFactory(null, sentIntent, null);
-                handleNotInService(ss, tracker);
-            }
-            return;
-        }
-
-        int refNumber = getNextConcatenatedRef() & 0x00FF;
-        int msgCount = parts.size();
-        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
-
-        mRemainingMessages = msgCount;
-
-        TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
-        for (int i = 0; i < msgCount; i++) {
-            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
-            if (encoding != details.codeUnitSize
-                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
-                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
-                encoding = details.codeUnitSize;
-            }
-            encodingForParts[i] = details;
-        }
-
-        for (int i = 0; i < msgCount; i++) {
-            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
-            concatRef.refNumber = refNumber;
-            concatRef.seqNumber = i + 1;  // 1-based sequence
-            concatRef.msgCount = msgCount;
-            concatRef.isEightBits = false;
-            SmsHeader smsHeader = new SmsHeader();
-            smsHeader.concatRef = concatRef;
-            if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
-                smsHeader.languageTable = encodingForParts[i].languageTable;
-                smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
-            }
-
-            PendingIntent sentIntent = null;
-            if (sentIntents != null && sentIntents.size() > i) {
-                sentIntent = sentIntents.get(i);
-            }
-
-            PendingIntent deliveryIntent = null;
-            if (deliveryIntents != null && deliveryIntents.size() > i) {
-                deliveryIntent = deliveryIntents.get(i);
-            }
-
-            SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
-                    parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
-                    encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
-
-            HashMap<String, Object> map = new HashMap<String, Object>();
-            map.put("smsc", pdus.encodedScAddress);
-            map.put("pdu", pdus.encodedMessage);
-
-            SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent);
-            sendSms(tracker);
-        }
+    /** {@inheritDoc} */
+    @Override
+    protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
+            String message, SmsHeader smsHeader, int encoding,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
+        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
+                message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
+                encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
+        sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
     }
 
     /** {@inheritDoc} */
@@ -353,45 +235,16 @@
         byte pdu[] = (byte[]) map.get("pdu");
 
         Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
-        mCm.sendSMS(IccUtils.bytesToHexString(smsc),
-                IccUtils.bytesToHexString(pdu), reply);
-    }
-
-    /**
-     * Send the multi-part SMS based on multipart Sms tracker
-     *
-     * @param tracker holds the multipart Sms tracker ready to be sent
-     */
-    @Override
-    protected void sendMultipartSms (SmsTracker tracker) {
-        ArrayList<String> parts;
-        ArrayList<PendingIntent> sentIntents;
-        ArrayList<PendingIntent> deliveryIntents;
-
-        HashMap<String, Object> map = tracker.mData;
-
-        String destinationAddress = (String) map.get("destination");
-        String scAddress = (String) map.get("scaddress");
-
-        parts = (ArrayList<String>) map.get("parts");
-        sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents");
-        deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents");
-
-        sendMultipartTextWithPermit(destinationAddress,
-                scAddress, parts, sentIntents, deliveryIntents);
-
+        mCm.sendSMS(IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), reply);
     }
 
     /** {@inheritDoc} */
     @Override
-    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
-        // FIXME unit test leaves cm == null. this should change
-        if (mCm != null) {
-            mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
-        }
+    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
+        mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
     }
 
-    private int resultToCause(int rc) {
+    private static int resultToCause(int rc) {
         switch (rc) {
             case Activity.RESULT_OK:
             case Intents.RESULT_SMS_HANDLED:
@@ -485,10 +338,12 @@
     private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
             new HashMap<SmsCbConcatInfo, byte[][]>();
 
-    @Override
-    protected void handleBroadcastSms(AsyncResult ar) {
+    /**
+     * Handle 3GPP format SMS-CB message.
+     * @param ar the AsyncResult containing the received PDUs
+     */
+    private void handleBroadcastSms(AsyncResult ar) {
         try {
-            byte[][] pdus = null;
             byte[] receivedPdu = (byte[])ar.result;
 
             if (false) {
@@ -507,10 +362,11 @@
 
             SmsCbHeader header = new SmsCbHeader(receivedPdu);
             String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
-            GsmCellLocation cellLocation = (GsmCellLocation)mGsmPhone.getCellLocation();
+            GsmCellLocation cellLocation = (GsmCellLocation) mPhone.getCellLocation();
             int lac = cellLocation.getLac();
             int cid = cellLocation.getCid();
 
+            byte[][] pdus;
             if (header.nrOfPages > 1) {
                 // Multi-page message
                 SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid);
@@ -563,5 +419,4 @@
             Log.e(TAG, "Error in decoding SMS CB pdu", e);
         }
     }
-
 }
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
index 73c319c..5d6f181 100755
--- a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
@@ -38,6 +38,7 @@
 import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.SmsMessageBase;
 
 import java.util.ArrayList;
 
@@ -1160,6 +1161,15 @@
         }
     }
 
+    /**
+     * Dispatch 3GPP format message. Overridden for CDMA/LTE phones by
+     * {@link com.android.internal.telephony.cdma.CdmaLteUiccRecords}
+     * to send messages to the secondary 3GPP format SMS dispatcher.
+     */
+    protected int dispatchGsmMessage(SmsMessageBase message) {
+        return phone.mSMS.dispatchMessage(message);
+    }
+
     private void handleSms(byte[] ba) {
         if (ba[0] != 0)
             Log.d("ENF", "status : " + ba[0]);
@@ -1175,7 +1185,7 @@
             System.arraycopy(ba, 1, pdu, 0, n - 1);
             SmsMessage message = SmsMessage.createFromPdu(pdu);
 
-            phone.mSMS.dispatchMessage(message);
+            dispatchGsmMessage(message);
         }
     }
 
@@ -1201,7 +1211,7 @@
                 System.arraycopy(ba, 1, pdu, 0, n - 1);
                 SmsMessage message = SmsMessage.createFromPdu(pdu);
 
-                phone.mSMS.dispatchMessage(message);
+                dispatchGsmMessage(message);
 
                 // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
                 // 1 == "received by MS from network; message read"
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 3784e7c..ea030e6 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -137,14 +137,6 @@
     }
 
     /** @hide */
-    public static SmsMessage newFromCMTI(String line) {
-        // the thinking here is not to read the message immediately
-        // FTA test case
-        Log.e(LOG_TAG, "newFromCMTI: not yet supported");
-        return null;
-    }
-
-    /** @hide */
     public static SmsMessage newFromCDS(String line) {
         try {
             SmsMessage msg = new SmsMessage();
@@ -157,15 +149,6 @@
     }
 
     /**
-     * Note: This functionality is currently not supported in GSM mode.
-     * @hide
-     */
-    public static SmsMessageBase newFromParcel(Parcel p){
-        Log.w(LOG_TAG, "newFromParcel: is not supported in GSM mode.");
-        return null;
-    }
-
-    /**
      * Create an SmsMessage from an SMS EF record.
      *
      * @param index Index of SMS record. This should be index in ArrayList