Send outbound messages via the default carrier app. Initially, the
message will go into a pending state. There is a new API call to
update the status of a message. If the carrier app cannot send the
message, it will fall back to the default GSM/CDMA network.

This is the same cl as 476721 which has already been approved by jsh@

Change-Id: I51d732b9cc40b371f77fd26d28e0836a466afc71
diff --git a/src/java/android/provider/Telephony.java b/src/java/android/provider/Telephony.java
index 82ad215..2666d65 100644
--- a/src/java/android/provider/Telephony.java
+++ b/src/java/android/provider/Telephony.java
@@ -879,6 +879,42 @@
                 "android.provider.Telephony.SMS_REJECTED";
 
             /**
+             * Broadcast Action: A new SMS PDU needs to be sent from
+             * the device. This intent will only be delivered to the default
+             * carrier app. That app is responsible for sending the PDU.
+             * The intent will have the following extra values:</p>
+             *
+             * <ul>
+             *   <li><em>"pdu"</em> - (byte[]) The PDU to send.</li>
+             *   <li><em>"smsc"</em> - (byte[]) The service center address (for GSM PDU only).</li>
+             *   <li><em>"format"</em> - (String) The format of the PDU. Either 3gpp or 3gpp2. </li>
+             * </ul>
+             *
+             * <p>If a BroadcastReceiver is trying to send the message,
+             *  it should set the result code to {@link android.app.Activity#RESULT_OK} and set
+             *  the following in the result extra values:</p>
+             *
+             * <ul>
+             *   <li><em>"messageref"</em> - (int) The new message reference number which will be
+             *   later used in the updateSmsSendStatus call.</li>
+             * </ul>
+             *
+             * <p>If a BroadcastReceiver cannot send the message, it should not set the result
+             *  code and the platform will send it via the normal pathway.
+             * </p>
+             *
+             * <p class="note"><strong>Note:</strong>
+             * The broadcast receiver that filters for this intent must declare
+             * {@link android.Manifest.permission#BROADCAST_SMS} as a required permission in
+             * the <a href="{@docRoot}guide/topics/manifest/receiver-element.html">{@code
+             * &lt;receiver>}</a> tag.
+             * @hide
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_SEND_ACTION =
+                "android.provider.Telephony.SMS_SEND";
+
+            /**
              * Read the PDUs out of an {@link #SMS_RECEIVED_ACTION} or a
              * {@link #DATA_SMS_RECEIVED_ACTION} intent.
              *
diff --git a/src/java/android/telephony/SmsManager.java b/src/java/android/telephony/SmsManager.java
index 3af756e..aff0bb2 100644
--- a/src/java/android/telephony/SmsManager.java
+++ b/src/java/android/telephony/SmsManager.java
@@ -105,6 +105,27 @@
     }
 
     /**
+     * Update the status of a pending (send-by-IP) SMS message and resend by PSTN if necessary.
+     * This outbound message was handled by the carrier app. If the carrier app fails to send
+     * this message, it would be resent by PSTN.
+     *
+     * @param messageRef the reference number of the SMS message.
+     * @param success True if and only if the message was sent successfully. If its value is
+     *  false, this message should be resent via PSTN.
+     * {@hide}
+     */
+    public void updateSmsSendStatus(int messageRef, boolean success) {
+        try {
+            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            if (iccISms != null) {
+                iccISms.updateSmsSendStatus(messageRef, success);
+            }
+        } catch (RemoteException ex) {
+          // ignore it
+        }
+    }
+
+    /**
      * Divide a message text into several fragments, none bigger than
      * the maximum SMS message size.
      *
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 938fd38..ce5b493 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -394,6 +394,21 @@
     }
 
     /**
+     * Update the status of a pending (send-by-IP) SMS message and resend by PSTN if necessary.
+     * This outbound message was handled by the carrier app. If the carrier app fails to send
+     * this message, it would be resent by PSTN.
+     *
+     * @param messageRef the reference number of the SMS message.
+     * @param success True if and only if the message was sent successfully. If its value is
+     *  false, this message should be resent via PSTN.
+     * {@hide}
+     */
+    @Override
+    public void updateSmsSendStatus(int messageRef, boolean success) {
+        mDispatcher.updateSmsSendStatus(messageRef, success);
+    }
+
+    /**
      * Send a multi-part text based SMS.
      *
      * @param destAddr the address to send the message to
diff --git a/src/java/com/android/internal/telephony/ImsSMSDispatcher.java b/src/java/com/android/internal/telephony/ImsSMSDispatcher.java
index 07bce94..2bc0c16 100644
--- a/src/java/com/android/internal/telephony/ImsSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSMSDispatcher.java
@@ -20,6 +20,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
@@ -189,6 +190,66 @@
     }
 
     @Override
+    protected void sendSmsByPstn(SmsTracker tracker) {
+        // This function should be defined in Gsm/CdmaDispatcher.
+        Rlog.e(TAG, "sendSmsByPstn should never be called from here!");
+    }
+
+    @Override
+    protected void updateSmsSendStatus(int messageRef, boolean success) {
+        if (isCdmaMo()) {
+            updateSmsSendStatusHelper(messageRef, mCdmaDispatcher.sendPendingList,
+                                      mCdmaDispatcher, success);
+            updateSmsSendStatusHelper(messageRef, mGsmDispatcher.sendPendingList,
+                                      null, success);
+        } else {
+            updateSmsSendStatusHelper(messageRef, mGsmDispatcher.sendPendingList,
+                                      mGsmDispatcher, success);
+            updateSmsSendStatusHelper(messageRef, mCdmaDispatcher.sendPendingList,
+                                      null, success);
+        }
+    }
+
+    /**
+     * Find a tracker in a list to update its status. If the status is successful,
+     * send an EVENT_SEND_SMS_COMPLETE message. Otherwise, resend the message by PSTN if
+     * feasible.
+     *
+     * @param messageRef the reference number of the tracker.
+     * @param sendPendingList the list of trackers to look into.
+     * @param smsDispatcher the dispatcher for resending the message by PSTN.
+     * @param success true iff the message was sent successfully.
+     */
+    private void updateSmsSendStatusHelper(int messageRef,
+                                           List<SmsTracker> sendPendingList,
+                                           SMSDispatcher smsDispatcher,
+                                           boolean success) {
+        synchronized (sendPendingList) {
+            for (int i = 0, count = sendPendingList.size(); i < count; i++) {
+                SmsTracker tracker = sendPendingList.get(i);
+                if (tracker.mMessageRef == messageRef) {
+                    // Found it.  Remove from list and broadcast.
+                    sendPendingList.remove(i);
+                    if (success) {
+                        Rlog.d(TAG, "Sending SMS by IP succeeded.");
+                        sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
+                                                  new AsyncResult(tracker, null, null)));
+                    } else {
+                        Rlog.d(TAG, "Sending SMS by IP failed.");
+                        if (smsDispatcher != null) {
+                            smsDispatcher.sendSmsByPstn(tracker);
+                        } else {
+                            Rlog.e(TAG, "No feasible way to send this SMS.");
+                        }
+                    }
+                    // Only expect to see one tracker matching this messageref.
+                    break;
+                }
+            }
+        }
+    }
+
+    @Override
     protected void sendText(String destAddr, String scAddr, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         Rlog.d(TAG, "sendText");
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 9cd5b70..5eb9162 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -20,6 +20,7 @@
 import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -34,12 +35,14 @@
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.provider.Telephony;
 import android.provider.Telephony.Sms;
+import android.provider.Telephony.Sms.Intents;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
@@ -61,7 +64,9 @@
 import com.android.internal.telephony.ImsSMSDispatcher;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -256,6 +261,10 @@
     /** Sent messages awaiting a delivery status report. */
     protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>();
 
+    /** Outgoing messages being handled by the carrier app. */
+    protected final List<SmsTracker> sendPendingList =
+        Collections.synchronizedList(new ArrayList<SmsTracker>());
+
     /**
      * Handles events coming from the phone stack. Overridden from handler.
      *
@@ -522,6 +531,55 @@
             boolean use7bitOnly);
 
     /**
+     * Update the status of a pending (send-by-IP) SMS message and resend by PSTN if necessary.
+     * This outbound message was handled by the carrier app. If the carrier app fails to send
+     * this message, it would be resent by PSTN.
+     *
+     * @param messageRef the reference number of the SMS message.
+     * @param success True if and only if the message was sent successfully. If its value is
+     *  false, this message should be resent via PSTN.
+     */
+    protected abstract void updateSmsSendStatus(int messageRef, boolean success);
+
+    /**
+     * Handler for a {@link GsmSMSDispatcher} or {@link CdmaSMSDispatcher} broadcast.
+     * If SMS sending is successfuly, sends EVENT_SEND_SMS_COMPLETE message. Otherwise,
+     * send the message via the GSM/CDMA network.
+     */
+    protected final class SMSDispatcherReceiver extends BroadcastReceiver {
+
+        private final SmsTracker mTracker;
+
+        public SMSDispatcherReceiver(SmsTracker tracker) {
+            mTracker = tracker;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intents.SMS_SEND_ACTION)) {
+                int rc = getResultCode();
+                if (rc == Activity.RESULT_OK) {
+                    Rlog.d(TAG, "Sending SMS by IP pending.");
+                    Bundle resultExtras = getResultExtras(false);
+                    if (resultExtras != null && resultExtras.containsKey("messageref")) {
+                        mTracker.mMessageRef = resultExtras.getInt("messageref");
+                        Rlog.d(TAG, "messageref = " + mTracker.mMessageRef);
+                    } else {
+                        Rlog.e(TAG, "Can't find messageref in result extras.");
+                    }
+                    sendPendingList.add(mTracker);
+                } else {
+                    Rlog.d(TAG, "Sending SMS by IP failed.");
+                    sendSmsByPstn(mTracker);
+                }
+            } else {
+                Rlog.e(TAG, "unexpected BroadcastReceiver action: " + action);
+            }
+        }
+    }
+
+    /**
      * Send a multi-part text based SMS.
      *
      * @param destAddr the address to send the message to
@@ -942,6 +1000,13 @@
     protected abstract void sendSms(SmsTracker tracker);
 
     /**
+     * Send the SMS via the PSTN network.
+     *
+     * @param tracker holds the Sms tracker ready to be sent
+     */
+    protected abstract void sendSmsByPstn(SmsTracker tracker);
+
+    /**
      * Retry the message along to the radio.
      *
      * @param tracker holds the SMS message to send
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index f168180..0bfc245 100755
--- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -17,12 +17,18 @@
 package com.android.internal.telephony.cdma;
 
 import android.app.Activity;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemProperties;
 import android.provider.Telephony.Sms;
+import android.provider.Telephony.Sms.Intents;
 import android.telephony.Rlog;
 import android.telephony.SmsManager;
 
@@ -180,14 +186,43 @@
         // byte[] smsc = (byte[]) map.get("smsc");  // unused for CDMA
         byte[] pdu = (byte[]) map.get("pdu");
 
-        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
-
         Rlog.d(TAG, "sendSms: "
-                +" isIms()="+isIms()
-                +" mRetryCount="+tracker.mRetryCount
-                +" mImsRetry="+tracker.mImsRetry
-                +" mMessageRef="+tracker.mMessageRef
-                +" SS=" +mPhone.getServiceState().getState());
+                + " isIms()=" + isIms()
+                + " mRetryCount=" + tracker.mRetryCount
+                + " mImsRetry=" + tracker.mImsRetry
+                + " mMessageRef=" + tracker.mMessageRef
+                + " SS=" + mPhone.getServiceState().getState());
+
+        // FIX this once the carrier app and SIM restricted API is finalized.
+        // We should direct the intent to only the default carrier app.
+
+        // Send SMS via the carrier app.
+        BroadcastReceiver resultReceiver = new SMSDispatcherReceiver(tracker);
+
+        // Direct the intent to only the default carrier app.
+        Intent intent = new Intent(Intents.SMS_SEND_ACTION);
+        intent.putExtra("pdu", pdu);
+        intent.putExtra("format", getFormat());
+        intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+        Rlog.d(TAG, "Sending SMS by carrier app.");
+
+        mContext.sendOrderedBroadcast(intent, android.Manifest.permission.RECEIVE_SMS,
+                                      AppOpsManager.OP_RECEIVE_SMS, resultReceiver,
+                                      null, Activity.RESULT_CANCELED, null, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void updateSmsSendStatus(int messageRef, boolean success) {
+        // This function should be defined in ImsDispatcher.
+        Rlog.e(TAG, "updateSmsSendStatus should never be called from here!");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void sendSmsByPstn(SmsTracker tracker) {
+        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
+        byte[] pdu = (byte[]) tracker.mData.get("pdu");
 
         // sms over cdma is used:
         //   if sms over IMS is not supported AND
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 345abba..26dfa00 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -17,8 +17,12 @@
 package com.android.internal.telephony.gsm;
 
 import android.app.Activity;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncResult;
 import android.os.Message;
@@ -211,11 +215,8 @@
     protected void sendSms(SmsTracker tracker) {
         HashMap<String, Object> map = tracker.mData;
 
-        byte smsc[] = (byte[]) map.get("smsc");
         byte pdu[] = (byte[]) map.get("pdu");
 
-        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
-
         if (tracker.mRetryCount > 0) {
             Rlog.d(TAG, "sendSms: "
                     + " mRetryCount=" + tracker.mRetryCount
@@ -231,11 +232,38 @@
             }
         }
         Rlog.d(TAG, "sendSms: "
-                +" isIms()="+isIms()
-                +" mRetryCount="+tracker.mRetryCount
-                +" mImsRetry="+tracker.mImsRetry
-                +" mMessageRef="+tracker.mMessageRef
-                +" SS=" +mPhone.getServiceState().getState());
+                + " isIms()=" + isIms()
+                + " mRetryCount=" + tracker.mRetryCount
+                + " mImsRetry=" + tracker.mImsRetry
+                + " mMessageRef=" + tracker.mMessageRef
+                + " SS=" + mPhone.getServiceState().getState());
+
+        // FIX this once the carrier app and SIM restricted API is finalized.
+        // We should direct the intent to only the default carrier app.
+
+        // Send SMS via the carrier app.
+        BroadcastReceiver resultReceiver = new SMSDispatcherReceiver(tracker);
+
+        Intent intent = new Intent(Intents.SMS_SEND_ACTION);
+        intent.putExtra("pdu", pdu);
+        intent.putExtra("smsc", (byte[]) map.get("smsc"));
+        intent.putExtra("format", getFormat());
+        intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+        Rlog.d(TAG, "Sending SMS by carrier app.");
+
+        mContext.sendOrderedBroadcast(intent, android.Manifest.permission.RECEIVE_SMS,
+                                      AppOpsManager.OP_RECEIVE_SMS, resultReceiver,
+                                      null, Activity.RESULT_CANCELED, null, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void sendSmsByPstn(SmsTracker tracker) {
+        HashMap<String, Object> map = tracker.mData;
+
+        byte smsc[] = (byte[]) map.get("smsc");
+        byte[] pdu = (byte[]) map.get("pdu");
+        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
 
         // sms over gsm is used:
         //   if sms over IMS is not supported AND
@@ -263,6 +291,13 @@
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
+    protected void updateSmsSendStatus(int messageRef, boolean success) {
+        // This function should be defined in ImsDispatcher.
+        Rlog.e(TAG, "updateSmsSendStatus should never be called from here!");
+    }
+
     protected UiccCardApplication getUiccCardApplication() {
         return mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP);
     }