From ccedca0a4907591d994f7412adba2555c138bd0d Mon Sep 17 00:00:00 2001 From: arammelk Date: Thu, 24 Aug 2017 12:37:19 -0700 Subject: Add synchronous waitForSms (#76) --- .../android/mobly/snippet/bundled/SmsSnippet.java | 58 +++++++++++----------- .../android/mobly/snippet/bundled/utils/Utils.java | 43 ++++++++++++++++ 2 files changed, 73 insertions(+), 28 deletions(-) (limited to 'src/main/java/com/google/android/mobly/snippet/bundled') diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/SmsSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/SmsSnippet.java index 2eb8f38..4a38798 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/SmsSnippet.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/SmsSnippet.java @@ -29,16 +29,14 @@ import android.provider.Telephony.Sms.Intents; import android.support.test.InstrumentationRegistry; import android.telephony.SmsManager; import android.telephony.SmsMessage; - import com.google.android.mobly.snippet.Snippet; +import com.google.android.mobly.snippet.bundled.utils.Utils; import com.google.android.mobly.snippet.event.EventCache; import com.google.android.mobly.snippet.event.SnippetEvent; import com.google.android.mobly.snippet.rpc.AsyncRpc; import com.google.android.mobly.snippet.rpc.Rpc; - import java.util.ArrayList; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.TimeUnit; +import org.json.JSONObject; /** Snippet class for SMS RPCs. */ public class SmsSnippet implements Snippet { @@ -46,12 +44,12 @@ public class SmsSnippet implements Snippet { private static class SmsSnippetException extends Exception { private static final long serialVersionUID = 1L; - public SmsSnippetException(String msg) { + SmsSnippetException(String msg) { super(msg); } } - private static final int MAX_CHAR_COUNT_PER_SMS = 160; + private static final int MAX_CHAR_COUNT_PER_SMS = 160; private static final String SMS_SENT_ACTION = ".SMS_SENT"; private static final int DEFAULT_TIMEOUT_MILLISECOND = 60 * 1000; private static final String SMS_RECEIVED_EVENT_NAME = "ReceivedSms"; @@ -71,13 +69,12 @@ public class SmsSnippet implements Snippet { /** * Send SMS and return after waiting for send confirmation (with a timeout of 60 seconds). * - * @param phoneNumber A String representing phone number with country code. + * @param phoneNumber A String representing phone number with country code. * @param message A String representing the message to send. * @throws SmsSnippetException on SMS send error. */ @Rpc(description = "Send SMS to a specified phone number.") - public void sendSms(String phoneNumber, String message) - throws SmsSnippetException { + public void sendSms(String phoneNumber, String message) throws Throwable { String callbackId = SMS_CALLBACK_ID_PREFIX + (++mCallbackCounter); OutboundSmsReceiver receiver = new OutboundSmsReceiver(mContext, callbackId); @@ -85,33 +82,25 @@ public class SmsSnippet implements Snippet { ArrayList parts = mSmsManager.divideMessage(message); ArrayList sIntents = new ArrayList<>(); for (int i = 0; i < parts.size(); i++) { - sIntents.add(PendingIntent.getBroadcast( - mContext, 0, new Intent(SMS_SENT_ACTION), 0)); + sIntents.add( + PendingIntent.getBroadcast(mContext, 0, new Intent(SMS_SENT_ACTION), 0)); } receiver.setExpectedMessageCount(parts.size()); mContext.registerReceiver(receiver, new IntentFilter(SMS_SENT_ACTION)); mSmsManager.sendMultipartTextMessage(phoneNumber, null, parts, sIntents, null); } else { - PendingIntent sentIntent = PendingIntent.getBroadcast( - mContext, 0, new Intent(SMS_SENT_ACTION), 0); + PendingIntent sentIntent = + PendingIntent.getBroadcast(mContext, 0, new Intent(SMS_SENT_ACTION), 0); receiver.setExpectedMessageCount(1); mContext.registerReceiver(receiver, new IntentFilter(SMS_SENT_ACTION)); mSmsManager.sendTextMessage(phoneNumber, null, message, sentIntent, null); } - String qId = EventCache.getQueueId(callbackId, SMS_SENT_EVENT_NAME); - LinkedBlockingDeque q = EventCache.getInstance().getEventDeque(qId); - SnippetEvent result; - - try { - result = q.pollFirst(DEFAULT_TIMEOUT_MILLISECOND, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - throw new SmsSnippetException("Did not receive SMS sent confirmation event."); - } + SnippetEvent result = + Utils.waitForSnippetEvent( + callbackId, SMS_SENT_EVENT_NAME, DEFAULT_TIMEOUT_MILLISECOND); - if (result == null) { - throw new SmsSnippetException("Timed out waiting for SMS sent confirmation event."); - } else if (result.getData().containsKey("error")) { + if (result.getData().containsKey("error")) { throw new SmsSnippetException( "Failed to send SMS, error code: " + result.getData().getInt("error")); } @@ -124,6 +113,17 @@ public class SmsSnippet implements Snippet { mContext.registerReceiver(receiver, new IntentFilter(Intents.SMS_RECEIVED_ACTION)); } + @TargetApi(Build.VERSION_CODES.KITKAT) + @Rpc(description = "Wait for incoming SMS message.") + public JSONObject waitForSms() throws Throwable { + String callbackId = SMS_CALLBACK_ID_PREFIX + (++mCallbackCounter); + SmsReceiver receiver = new SmsReceiver(mContext, callbackId); + mContext.registerReceiver(receiver, new IntentFilter(Intents.SMS_RECEIVED_ACTION)); + return Utils.waitForSnippetEvent( + callbackId, SMS_RECEIVED_EVENT_NAME, DEFAULT_TIMEOUT_MILLISECOND) + .toJson(); + } + @Override public void shutdown() {} @@ -140,7 +140,9 @@ public class SmsSnippet implements Snippet { mExpectedMessageCount = 0; } - public void setExpectedMessageCount(int count) { mExpectedMessageCount = count; } + public void setExpectedMessageCount(int count) { + mExpectedMessageCount = count; + } @Override public void onReceive(Context context, Intent intent) { @@ -148,7 +150,7 @@ public class SmsSnippet implements Snippet { if (SMS_SENT_ACTION.equals(action)) { SnippetEvent event = new SnippetEvent(mCallbackId, SMS_SENT_EVENT_NAME); - switch(getResultCode()) { + switch (getResultCode()) { case Activity.RESULT_OK: if (mExpectedMessageCount == 1) { event.getData().putBoolean("sent", true); @@ -156,7 +158,7 @@ public class SmsSnippet implements Snippet { mContext.unregisterReceiver(this); } - if (mExpectedMessageCount > 0 ) { + if (mExpectedMessageCount > 0) { mExpectedMessageCount--; } break; diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/Utils.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/Utils.java index 8f4f7d6..c68ae5a 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/Utils.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/Utils.java @@ -16,10 +16,17 @@ package com.google.android.mobly.snippet.bundled.utils; +import com.google.android.mobly.snippet.bundled.SmsSnippet; +import com.google.android.mobly.snippet.event.EventCache; +import com.google.android.mobly.snippet.event.SnippetEvent; import com.google.common.primitives.Primitives; import com.google.common.reflect.TypeToken; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Locale; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public final class Utils { @@ -55,6 +62,42 @@ public final class Utils { return false; } + /** + * Wait on a specific snippet event. + * + *

This allows a snippet to wait on another SnippetEvent as long as they know the name and + * callback id. Commonly used to make async calls synchronous, see {@link + * SmsSnippet#waitForSms()} waitForSms} for example usage. + * + * @param callbackId String callbackId that we want to wait on. + * @param eventName String event name that we are waiting on. + * @param timeout int timeout in milliseconds for how long it will wait for the event. + * @return SnippetEvent if one was received. + * @throws Throwable if interrupted while polling for event completion. Throws TimeoutException + * if no snippet event is received. + */ + public static SnippetEvent waitForSnippetEvent( + String callbackId, String eventName, Integer timeout) throws Throwable { + String qId = EventCache.getQueueId(callbackId, eventName); + LinkedBlockingDeque q = EventCache.getInstance().getEventDeque(qId); + SnippetEvent result; + try { + result = q.pollFirst(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw e.getCause(); + } + + if (result == null) { + throw new TimeoutException( + String.format( + Locale.ROOT, + "Timed out waiting(%d millis) for SnippetEvent: %s", + timeout, + callbackId)); + } + return result; + } + /** * A function interface that is used by lambda functions signaling an async operation is still * going on. -- cgit v1.2.3