diff --git a/embedding/android/GeckoApp.java b/embedding/android/GeckoApp.java index ca156a74466..6db4339b191 100644 --- a/embedding/android/GeckoApp.java +++ b/embedding/android/GeckoApp.java @@ -417,6 +417,7 @@ abstract public class GeckoApp IntentFilter smsFilter = new IntentFilter(); smsFilter.addAction(GeckoSmsManager.ACTION_SMS_RECEIVED); smsFilter.addAction(GeckoSmsManager.ACTION_SMS_SENT); + smsFilter.addAction(GeckoSmsManager.ACTION_SMS_DELIVERED); mSmsReceiver = new GeckoSmsManager(); registerReceiver(mSmsReceiver, smsFilter); diff --git a/embedding/android/GeckoAppShell.java b/embedding/android/GeckoAppShell.java index 5891246a28b..ab59d670658 100644 --- a/embedding/android/GeckoAppShell.java +++ b/embedding/android/GeckoAppShell.java @@ -122,7 +122,9 @@ public class GeckoAppShell public static native void notifyBatteryChange(double aLevel, boolean aCharging, double aRemainingTime); public static native void notifySmsReceived(String aSender, String aBody, long aTimestamp); - public static native void onSmsSent(String aReceiver, String aBody, long aTimestamp); + public static native int saveMessageInSentbox(String aReceiver, String aBody, long aTimestamp); + public static native void notifySmsSent(int aId, String aReceiver, String aBody, long aTimestamp); + public static native void notifySmsDelivered(int aId, String aReceiver, String aBody, long aTimestamp); // A looper thread, accessed by GeckoAppShell.getHandler private static class LooperThread extends Thread { diff --git a/embedding/android/GeckoSmsManager.java b/embedding/android/GeckoSmsManager.java index cb9d590723c..6da1f1a2e09 100644 --- a/embedding/android/GeckoSmsManager.java +++ b/embedding/android/GeckoSmsManager.java @@ -77,30 +77,77 @@ class PendingIntentUID */ class Envelope { - protected int mId; - protected int mRemainingParts; - protected boolean mFailing; + enum SubParts { + SENT_PART, + DELIVERED_PART + } + + protected int mId; + protected int mMessageId; + protected long mMessageTimestamp; + + /** + * Number of sent/delivered remaining parts. + * @note The array has much slots as SubParts items. + */ + protected int[] mRemainingParts; + + /** + * Whether sending/delivering is currently failing. + * @note The array has much slots as SubParts items. + */ + protected boolean[] mFailing; public Envelope(int aId, int aParts) { mId = aId; - mRemainingParts = aParts; - mFailing = false; + mMessageId = -1; + mMessageTimestamp = 0; + + int size = Envelope.SubParts.values().length; + mRemainingParts = new int[size]; + mFailing = new boolean[size]; + + for (int i=0; i + mRemainingParts[SubParts.DELIVERED_PART.ordinal()]) { + Log.e("GeckoSmsManager", "Delivered more parts than we sent!?"); + } } - public boolean arePartsRemaining() { - return mRemainingParts != 0; + public boolean arePartsRemaining(Envelope.SubParts aType) { + return mRemainingParts[aType.ordinal()] != 0; } - public void markAsFailed() { - mFailing = true; + public void markAsFailed(Envelope.SubParts aType) { + mFailing[aType.ordinal()] = true; } - public boolean isFailing() { - return mFailing; + public boolean isFailing(Envelope.SubParts aType) { + return mFailing[aType.ordinal()]; + } + + public int getMessageId() { + return mMessageId; + } + + public void setMessageId(int aMessageId) { + mMessageId = aMessageId; + } + + public long getMessageTimestamp() { + return mMessageTimestamp; + } + + public void setMessageTimestamp(long aMessageTimestamp) { + mMessageTimestamp = aMessageTimestamp; } } @@ -168,8 +215,10 @@ class Postman public class GeckoSmsManager extends BroadcastReceiver { - public final static String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"; - public final static String ACTION_SMS_SENT = "org.mozilla.gecko.SMS_SENT"; + public final static String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"; + public final static String ACTION_SMS_SENT = "org.mozilla.gecko.SMS_SENT"; + public final static String ACTION_SMS_DELIVERED = "org.mozilla.gecko.SMS_DELIVERED"; + private final static int kMaxMessageSize = 160; @Override @@ -200,7 +249,8 @@ public class GeckoSmsManager return; } - if (intent.getAction().equals(ACTION_SMS_SENT)) { + if (intent.getAction().equals(ACTION_SMS_SENT) || + intent.getAction().equals(ACTION_SMS_DELIVERED)) { Bundle bundle = intent.getExtras(); if (bundle == null || !bundle.containsKey("envelopeId") || @@ -211,31 +261,66 @@ public class GeckoSmsManager int envelopeId = bundle.getInt("envelopeId"); Postman postman = Postman.getInstance(); - Envelope envelope = postman.getEnvelope(envelopeId); - envelope.decreaseRemainingParts(); + Envelope envelope = postman.getEnvelope(envelopeId); + if (envelope == null) { + Log.e("GeckoSmsManager", "Got an invalid envelope id (or Envelope has been destroyed)!"); + return; + } + + Envelope.SubParts part = intent.getAction().equals(ACTION_SMS_SENT) + ? Envelope.SubParts.SENT_PART + : Envelope.SubParts.DELIVERED_PART; + envelope.decreaseRemainingParts(part); + if (getResultCode() != Activity.RESULT_OK) { // TODO: manage error types. Log.i("GeckoSmsManager", "SMS part sending failed!"); - envelope.markAsFailed(); + envelope.markAsFailed(part); } - if (envelope.arePartsRemaining()) { + if (envelope.arePartsRemaining(part)) { return; } - if (envelope.isFailing()) { - // TODO: inform about the send failure. - Log.i("GeckoSmsManager", "SMS sending failed!"); + if (envelope.isFailing(part)) { + if (part == Envelope.SubParts.SENT_PART) { + // TODO: inform about the send failure. + Log.i("GeckoSmsManager", "SMS sending failed!"); + } else { + // It seems unlikely to get a result code for a failure to deliver. + // Even if, we don't want to do anything with this. + Log.e("GeckoSmsManager", "SMS failed to be delivered... is that even possible?"); + } } else { - GeckoAppShell.onSmsSent(bundle.getString("number"), - bundle.getString("message"), - System.currentTimeMillis()); - Log.i("GeckoSmsManager", "SMS sending was successfull!"); + if (part == Envelope.SubParts.SENT_PART) { + String number = bundle.getString("number"); + String message = bundle.getString("message"); + long timestamp = System.currentTimeMillis(); + + int id = GeckoAppShell.saveMessageInSentbox(number, message, timestamp); + + GeckoAppShell.notifySmsSent(id, number, message, timestamp); + + envelope.setMessageId(id); + envelope.setMessageTimestamp(timestamp); + + Log.i("GeckoSmsManager", "SMS sending was successfull!"); + } else { + GeckoAppShell.notifySmsDelivered(envelope.getMessageId(), + bundle.getString("number"), + bundle.getString("message"), + envelope.getMessageTimestamp()); + Log.i("GeckoSmsManager", "SMS succesfully delivered!"); + } } - postman.destroyEnvelope(envelopeId); + // Destroy the envelope object only if the SMS has been sent and delivered. + if (!envelope.arePartsRemaining(Envelope.SubParts.SENT_PART) && + !envelope.arePartsRemaining(Envelope.SubParts.DELIVERED_PART)) { + postman.destroyEnvelope(envelopeId); + } return; } @@ -257,15 +342,18 @@ public class GeckoSmsManager SmsManager sm = SmsManager.getDefault(); Intent sentIntent = new Intent(ACTION_SMS_SENT); + Intent deliveredIntent = new Intent(ACTION_SMS_DELIVERED); + Bundle bundle = new Bundle(); bundle.putString("number", aNumber); bundle.putString("message", aMessage); if (aMessage.length() <= kMaxMessageSize) { envelopeId = Postman.getInstance().createEnvelope(1); - bundle.putInt("envelopeId", envelopeId); + sentIntent.putExtras(bundle); + deliveredIntent.putExtras(bundle); /* * There are a few things to know about getBroadcast and pending intents: @@ -283,16 +371,25 @@ public class GeckoSmsManager PendingIntentUID.generate(), sentIntent, PendingIntent.FLAG_CANCEL_CURRENT); - sm.sendTextMessage(aNumber, "", aMessage, sentPendingIntent, null); + PendingIntent deliveredPendingIntent = + PendingIntent.getBroadcast(GeckoApp.surfaceView.getContext(), + PendingIntentUID.generate(), deliveredIntent, + PendingIntent.FLAG_CANCEL_CURRENT); + + sm.sendTextMessage(aNumber, "", aMessage, + sentPendingIntent, deliveredPendingIntent); } else { ArrayList parts = sm.divideMessage(aMessage); envelopeId = Postman.getInstance().createEnvelope(parts.size()); - bundle.putInt("envelopeId", envelopeId); + sentIntent.putExtras(bundle); + deliveredIntent.putExtras(bundle); ArrayList sentPendingIntents = new ArrayList(parts.size()); + ArrayList deliveredPendingIntents = + new ArrayList(parts.size()); for (int i=0; iNewString(PromiseFlatString(aRecipient).get(), aRecipient.Length()); jstring jBody = GetJNIForThread()->NewString(PromiseFlatString(aBody).get(), aBody.Length()); return GetJNIForThread()->CallStaticIntMethod(mGeckoAppShellClass, jSaveSentMessage, jRecipient, jBody, aDate); diff --git a/widget/android/AndroidJNI.cpp b/widget/android/AndroidJNI.cpp index 56d93ad8917..d7648321e42 100644 --- a/widget/android/AndroidJNI.cpp +++ b/widget/android/AndroidJNI.cpp @@ -86,7 +86,9 @@ extern "C" { NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyUriVisited(JNIEnv *, jclass, jstring uri); NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyBatteryChange(JNIEnv* jenv, jclass, jdouble, jboolean, jdouble); NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifySmsReceived(JNIEnv* jenv, jclass, jstring, jstring, jlong); - NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onSmsSent(JNIEnv* jenv, jclass, jstring, jstring, jlong); + NS_EXPORT PRInt32 JNICALL Java_org_mozilla_gecko_GeckoAppShell_saveMessageInSentbox(JNIEnv* jenv, jclass, jstring, jstring, jlong); + NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifySmsSent(JNIEnv* jenv, jclass, jint, jstring, jstring, jlong); + NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifySmsDelivered(JNIEnv* jenv, jclass, jint, jstring, jstring, jlong); #ifdef MOZ_JAVA_COMPOSITOR NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_bindWidgetTexture(JNIEnv* jenv, jclass); @@ -275,56 +277,98 @@ Java_org_mozilla_gecko_GeckoAppShell_notifySmsReceived(JNIEnv* jenv, jclass, NS_DispatchToMainThread(runnable); } -NS_EXPORT void JNICALL -Java_org_mozilla_gecko_GeckoAppShell_onSmsSent(JNIEnv* jenv, jclass, - jstring aReceiver, - jstring aBody, - jlong aTimestamp) +NS_EXPORT PRInt32 JNICALL +Java_org_mozilla_gecko_GeckoAppShell_saveMessageInSentbox(JNIEnv* jenv, jclass, + jstring aReceiver, + jstring aBody, + jlong aTimestamp) { - class OnSmsSentRunnable : public nsRunnable { + nsCOMPtr smsDBService = + do_GetService(SMS_DATABASE_SERVICE_CONTRACTID); + + if (!smsDBService) { + NS_ERROR("Sms Database Service not available!"); + return -1; + } + + PRInt32 id; + smsDBService->SaveSentMessage(nsJNIString(aReceiver, jenv), + nsJNIString(aBody, jenv), aTimestamp, &id); + + return id; +} + +NS_EXPORT void JNICALL +Java_org_mozilla_gecko_GeckoAppShell_notifySmsSent(JNIEnv* jenv, jclass, + jint aId, + jstring aReceiver, + jstring aBody, + jlong aTimestamp) +{ + class NotifySmsSentRunnable : public nsRunnable { public: - OnSmsSentRunnable(const nsAString& aReceiver, const nsAString& aBody, PRUint64 aTimestamp) - : mReceiver(aReceiver) - , mBody(aBody) - , mTimestamp(aTimestamp) + NotifySmsSentRunnable(const SmsMessageData& aMessageData) + : mMessageData(aMessageData) {} NS_IMETHODIMP Run() { - nsCOMPtr smsDBService = - do_GetService(SMS_DATABASE_SERVICE_CONTRACTID); - - if (!smsDBService) { - NS_ERROR("Sms Database Service not available!"); + nsCOMPtr obs = services::GetObserverService(); + if (!obs) { return NS_OK; } - int id; - smsDBService->SaveSentMessage(mReceiver, mBody, mTimestamp, &id); - - nsCOMPtr message = - new SmsMessage(id, eDeliveryState_Sent, EmptyString(), - mReceiver, mBody, mTimestamp); - - nsCOMPtr obs = services::GetObserverService(); - if (!obs) { - NS_ERROR("Observer Service not available!"); - return NS_OK; - } - + nsCOMPtr message = new SmsMessage(mMessageData); obs->NotifyObservers(message, kSmsSentObserverTopic, nsnull); return NS_OK; } private: - nsString mReceiver; - nsString mBody; - PRUint64 mTimestamp; + SmsMessageData mMessageData; }; - nsCOMPtr runnable = - new OnSmsSentRunnable(nsJNIString(aReceiver, jenv), - nsJNIString(aBody, jenv), aTimestamp); + SmsMessageData message(aId, eDeliveryState_Sent, EmptyString(), + nsJNIString(aReceiver, jenv), + nsJNIString(aBody, jenv), aTimestamp); + + nsCOMPtr runnable = new NotifySmsSentRunnable(message); + NS_DispatchToMainThread(runnable); +} + +NS_EXPORT void JNICALL +Java_org_mozilla_gecko_GeckoAppShell_notifySmsDelivered(JNIEnv* jenv, jclass, + jint aId, + jstring aReceiver, + jstring aBody, + jlong aTimestamp) +{ + class NotifySmsDeliveredRunnable : public nsRunnable { + public: + NotifySmsDeliveredRunnable(const SmsMessageData& aMessageData) + : mMessageData(aMessageData) + {} + + NS_IMETHODIMP Run() { + nsCOMPtr obs = services::GetObserverService(); + if (!obs) { + return NS_OK; + } + + nsCOMPtr message = new SmsMessage(mMessageData); + obs->NotifyObservers(message, kSmsDeliveredObserverTopic, nsnull); + + return NS_OK; + } + + private: + SmsMessageData mMessageData; + }; + + SmsMessageData message(aId, eDeliveryState_Sent, EmptyString(), + nsJNIString(aReceiver, jenv), + nsJNIString(aBody, jenv), aTimestamp); + + nsCOMPtr runnable = new NotifySmsDeliveredRunnable(message); NS_DispatchToMainThread(runnable); }