Return pending intent to service caller if MmsService can't be connected

If MmsService can not be connected, broker throws a runtime exception
which does not return to service caller via PendingIntent. MMS app would
wait forever for the return of sent/downloaded PendingIntent. This
change makes sure PendingIntent is always returned so MMS app can be
unblocked.

b/19732709

Change-Id: I694a0fc29bb431b48c7077e7a323844709ae695f
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index 0de6a03..9409615 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -36,6 +36,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.carrier.CarrierMessagingService;
+import android.telephony.SmsManager;
 import android.telephony.TelephonyManager;
 import android.util.Slog;
 
@@ -111,6 +112,106 @@
         }
     };
 
+    // Instance of IMms for returning failure to service API caller,
+    // used when MmsService cannot be connected.
+    private final IMms mServiceStubForFailure = new IMms() {
+
+        @Override
+        public IBinder asBinder() {
+            return null;
+        }
+
+        @Override
+        public void sendMessage(int subId, String callingPkg, Uri contentUri, String locationUrl,
+                Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
+            returnPendingIntentWithError(sentIntent);
+        }
+
+        @Override
+        public void downloadMessage(int subId, String callingPkg, String locationUrl,
+                Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent)
+                throws RemoteException {
+            returnPendingIntentWithError(downloadedIntent);
+        }
+
+        @Override
+        public Bundle getCarrierConfigValues(int subId) throws RemoteException {
+            return null;
+        }
+
+        @Override
+        public Uri importTextMessage(String callingPkg, String address, int type, String text,
+                long timestampMillis, boolean seen, boolean read) throws RemoteException {
+            return null;
+        }
+
+        @Override
+        public Uri importMultimediaMessage(String callingPkg, Uri contentUri, String messageId,
+                long timestampSecs, boolean seen, boolean read) throws RemoteException {
+            return null;
+        }
+
+        @Override
+        public boolean deleteStoredMessage(String callingPkg, Uri messageUri)
+                throws RemoteException {
+            return false;
+        }
+
+        @Override
+        public boolean deleteStoredConversation(String callingPkg, long conversationId)
+                throws RemoteException {
+            return false;
+        }
+
+        @Override
+        public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri,
+                ContentValues statusValues) throws RemoteException {
+            return false;
+        }
+
+        @Override
+        public boolean archiveStoredConversation(String callingPkg, long conversationId,
+                boolean archived) throws RemoteException {
+            return false;
+        }
+
+        @Override
+        public Uri addTextMessageDraft(String callingPkg, String address, String text)
+                throws RemoteException {
+            return null;
+        }
+
+        @Override
+        public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
+                throws RemoteException {
+            return null;
+        }
+
+        @Override
+        public void sendStoredMessage(int subId, String callingPkg, Uri messageUri,
+                Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
+            returnPendingIntentWithError(sentIntent);
+        }
+
+        @Override
+        public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException {
+            // Do nothing
+        }
+
+        @Override
+        public boolean getAutoPersisting() throws RemoteException {
+            return false;
+        }
+
+        private void returnPendingIntentWithError(PendingIntent pendingIntent) {
+            try {
+                pendingIntent.send(mContext, SmsManager.MMS_ERROR_UNSPECIFIED, null);
+            } catch (PendingIntent.CanceledException e) {
+                Slog.e(TAG, "Failed to return pending intent result", e);
+            }
+        }
+    };
+
     public MmsServiceBroker(Context context) {
         super(context);
         mContext = context;
@@ -145,44 +246,51 @@
         }
     }
 
-    private void ensureService() {
+    private IMms getOrConnectService() {
         synchronized (this) {
-            if (mService == null) {
-                // Service is not connected. Try blocking connecting.
-                Slog.w(TAG, "MmsService not connected. Try connecting...");
-                mConnectionHandler.sendMessage(
-                        mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING));
-                final long shouldEnd =
-                        SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS;
-                long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS;
-                while (waitTime > 0) {
-                    try {
-                        // TODO: consider using Java concurrent construct instead of raw object wait
-                        this.wait(waitTime);
-                    } catch (InterruptedException e) {
-                        Slog.w(TAG, "Connection wait interrupted", e);
-                    }
-                    if (mService != null) {
-                        // Success
-                        return;
-                    }
-                    // Calculate remaining waiting time to make sure we wait the full timeout period
-                    waitTime = shouldEnd - SystemClock.elapsedRealtime();
-                }
-                // Timed out. Something's really wrong.
-                Slog.e(TAG, "Can not connect to MmsService (timed out)");
-                throw new RuntimeException("Timed out in connecting to MmsService");
+            if (mService != null) {
+                return mService;
             }
+            // Service is not connected. Try blocking connecting.
+            Slog.w(TAG, "MmsService not connected. Try connecting...");
+            mConnectionHandler.sendMessage(
+                    mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING));
+            final long shouldEnd =
+                    SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS;
+            long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS;
+            while (waitTime > 0) {
+                try {
+                    // TODO: consider using Java concurrent construct instead of raw object wait
+                    this.wait(waitTime);
+                } catch (InterruptedException e) {
+                    Slog.w(TAG, "Connection wait interrupted", e);
+                }
+                if (mService != null) {
+                    // Success
+                    return mService;
+                }
+                // Calculate remaining waiting time to make sure we wait the full timeout period
+                waitTime = shouldEnd - SystemClock.elapsedRealtime();
+            }
+            // Timed out. Something's really wrong.
+            Slog.e(TAG, "Can not connect to MmsService (timed out)");
+            return null;
         }
     }
 
     /**
-     * Making sure when we obtain the mService instance it is always valid.
-     * Throws {@link RuntimeException} when it is empty.
+     * Make sure to return a non-empty service instance. Return the connected MmsService
+     * instance, if not connected, try connecting. If fail to connect, return a fake service
+     * instance which returns failure to service caller.
+     *
+     * @return a non-empty service instance, real or fake
      */
     private IMms getServiceGuarded() {
-        ensureService();
-        return mService;
+        final IMms service = getOrConnectService();
+        if (service != null) {
+            return service;
+        }
+        return mServiceStubForFailure;
     }
 
     private AppOpsManager getAppOpsManager() {