Skip to content

Commit dba39da

Browse files
authored
SMSManager Context and tests (#35)
* Started work on mocks and tests. Added SMSCore SmsManager as an optional parameter (so it can be tested) * Fixed null reference in SMSPeerTest class Implemented tests (using Mock objects) on some SMSHandler functions (could no test sent and delivered listeners) Added clearSetup() function (to be able to correctly test stuff) Fixed NullPointerException if in setup() was passed a null context * Improved null check using @nonnull Improved specifications (Code review by Luca Crema) * Improved SMSCore specifics. Re-added null check in SMSHandler setup() function due to a discussion in a code review. * Improved Mock test using verify(...) Removed useless "exception" class (Thanks to Matteo Carnelos for code review/improvement) * Optimized imports Formatted code * Removed context and useless methods context gets now asked by the methods that uses it improved specifications Added and fixed unit tests * Formatted class * Added author Tags
1 parent b7b1e05 commit dba39da

File tree

4 files changed

+112
-56
lines changed

4 files changed

+112
-56
lines changed

smslibrary/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
implementation 'com.github.CremaLuca:android-preferences:3.0'
3030
implementation 'androidx.appcompat:appcompat:1.1.0'
3131
testImplementation 'junit:junit:4.12'
32+
testImplementation 'org.mockito:mockito-core:1.10.19'
3233
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
3334
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
3435
implementation 'com.github.CremaLuca:android-preferences:2.3'

smslibrary/src/main/java/com/eis/smslibrary/SMSCore.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,31 @@
1212
* Wrapper for the {@link android.telephony} library.
1313
* This class is only used to interface with the core sms library of Android.
1414
*
15-
* @author Luca Crema
15+
* @author Luca Crema, Marco Cognolato
1616
*/
1717
final class SMSCore {
1818

19+
static SmsManager manager;
20+
21+
/**
22+
* Sets up a given valid custom manager
23+
* @param manager The custom manager to set up to send messages
24+
*
25+
* @author Marco Cognolato
26+
*/
27+
static void setManager(SmsManager manager){
28+
SMSCore.manager = manager;
29+
}
30+
31+
/**
32+
* @return Returns the pre-set up manager if not null, else returns the default manager
33+
*
34+
* @author Marco Cognolato
35+
*/
36+
private static SmsManager getManager(){
37+
return manager == null ? SmsManager.getDefault() : manager;
38+
}
39+
1940
/**
2041
* Calls the library method to send a single message
2142
*
@@ -25,7 +46,7 @@ final class SMSCore {
2546
* @param deliveredPI {@link PendingIntent} for a broadcast on message received, can be null
2647
*/
2748
static void sendMessage(@NonNull final String message, @NonNull final String phoneNumber, @Nullable final PendingIntent sentPI, @Nullable final PendingIntent deliveredPI) {
28-
SmsManager.getDefault().sendTextMessage(phoneNumber, null, message, sentPI, deliveredPI);
49+
getManager().sendTextMessage(phoneNumber, null, message, sentPI, deliveredPI);
2950
}
3051

3152
/**
@@ -37,7 +58,7 @@ static void sendMessage(@NonNull final String message, @NonNull final String pho
3758
* @param deliveredPIs {@link ArrayList} of pending intents for a broadcast on message delivered, can be null
3859
*/
3960
static void sendMessages(@NonNull final ArrayList<String> messages, @NonNull final String phoneNumber, @Nullable final ArrayList<PendingIntent> sentPIs, @Nullable final ArrayList<PendingIntent> deliveredPIs) {
40-
SmsManager.getDefault().sendMultipartTextMessage(phoneNumber, null, messages, sentPIs, deliveredPIs);
61+
getManager().sendMultipartTextMessage(phoneNumber, null, messages, sentPIs, deliveredPIs);
4162
}
4263

4364
}

smslibrary/src/main/java/com/eis/smslibrary/SMSManager.java

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,13 @@
1313
import com.eis.smslibrary.listeners.SMSReceivedServiceListener;
1414
import com.eis.smslibrary.listeners.SMSSentListener;
1515

16-
import java.lang.ref.WeakReference;
17-
1816
import it.lucacrema.preferences.PreferencesManager;
1917

20-
2118
/**
2219
* Communication handler for SMSs. It's a Singleton, you should
23-
* access it with {@link #getInstance}, and before doing anything you
24-
* should call {@link #setup}.<br/>
20+
* access it with {@link #getInstance}
2521
*
2622
* @author Luca Crema, Marco Mariotto, Alberto Ursino, Marco Tommasini, Marco Cognolato
27-
* @since 29/11/2019
2823
*/
2924
@SuppressWarnings({"WeakerAccess", "unused"})
3025
public class SMSManager implements CommunicationManager<SMSMessage> {
@@ -39,11 +34,9 @@ public class SMSManager implements CommunicationManager<SMSMessage> {
3934
private static SMSManager instance;
4035

4136
/**
42-
* Weak reference doesn't prevent garbage collector to
43-
* de-allocate this class when it has reference to a
44-
* context that is still running. Prevents memory leaks.
37+
* Received listener reference
4538
*/
46-
private WeakReference<Context> context;
39+
private SMSReceivedServiceListener receivedListener;
4740

4841
/**
4942
* This message counter is used so that we can have a different action name
@@ -70,15 +63,6 @@ public static SMSManager getInstance() {
7063
return instance;
7164
}
7265

73-
/**
74-
* Setup for the handler.
75-
*
76-
* @param context current context.
77-
*/
78-
public void setup(Context context) {
79-
this.context = new WeakReference<>(context);
80-
}
81-
8266
/**
8367
* Sends a message to a destination peer via SMS.
8468
* Requires {@link android.Manifest.permission#SEND_SMS}
@@ -87,7 +71,7 @@ public void setup(Context context) {
8771
*/
8872
@Override
8973
public void sendMessage(final @NonNull SMSMessage message) {
90-
sendMessage(message, null, null);
74+
sendMessage(message, null, null, null);
9175
}
9276

9377
/**
@@ -96,9 +80,12 @@ public void sendMessage(final @NonNull SMSMessage message) {
9680
*
9781
* @param message to be sent in the channel to a peer
9882
* @param sentListener called on message sent or on error, can be null
83+
* @param context The context of the application used to setup the listener
9984
*/
100-
public void sendMessage(final @NonNull SMSMessage message, final @Nullable SMSSentListener sentListener) {
101-
sendMessage(message, sentListener, null);
85+
public void sendMessage(final @NonNull SMSMessage message,
86+
final @Nullable SMSSentListener sentListener,
87+
Context context) {
88+
sendMessage(message, sentListener, null, context);
10289
}
10390

10491
/**
@@ -107,9 +94,12 @@ public void sendMessage(final @NonNull SMSMessage message, final @Nullable SMSSe
10794
*
10895
* @param message to be sent in the channel to a peer
10996
* @param deliveredListener called on message delivered or on error, can be null
97+
* @param context The context of the application used to setup the listener
11098
*/
111-
public void sendMessage(final @NonNull SMSMessage message, final @Nullable SMSDeliveredListener deliveredListener) {
112-
sendMessage(message, null, deliveredListener);
99+
public void sendMessage(final @NonNull SMSMessage message,
100+
final @Nullable SMSDeliveredListener deliveredListener,
101+
Context context) {
102+
sendMessage(message, null, deliveredListener, context);
113103
}
114104

115105
/**
@@ -119,13 +109,14 @@ public void sendMessage(final @NonNull SMSMessage message, final @Nullable SMSDe
119109
* @param message to be sent in the channel to a peer
120110
* @param sentListener called on message sent or on error, can be null
121111
* @param deliveredListener called on message delivered or on error, can be null
112+
* @param context The context of the application used to setup the listener
122113
*/
123114
public void sendMessage(final @NonNull SMSMessage message,
124115
final @Nullable SMSSentListener sentListener,
125-
final @Nullable SMSDeliveredListener deliveredListener) {
126-
checkSetup();
127-
PendingIntent sentPI = setupNewSentReceiver(message, sentListener);
128-
PendingIntent deliveredPI = setupNewDeliverReceiver(message, deliveredListener);
116+
final @Nullable SMSDeliveredListener deliveredListener,
117+
Context context) {
118+
PendingIntent sentPI = setupNewSentReceiver(message, sentListener, context);
119+
PendingIntent deliveredPI = setupNewDeliverReceiver(message, deliveredListener, context);
129120
SMSCore.sendMessage(getSMSContent(message), message.getPeer().getAddress(), sentPI, deliveredPI);
130121
}
131122

@@ -135,16 +126,19 @@ public void sendMessage(final @NonNull SMSMessage message,
135126
*
136127
* @param message that will be sent
137128
* @param listener to call on broadcast received
129+
* @param context The context of the application used to setup the listener
138130
* @return a {@link PendingIntent} to be passed to SMSCore
139131
*/
140-
private PendingIntent setupNewSentReceiver(final @NonNull SMSMessage message, final @Nullable SMSSentListener listener) {
141-
if (listener == null)
142-
return null; //Doesn't make any sense to have a BroadcastReceiver if there is no listener
132+
private PendingIntent setupNewSentReceiver(final @NonNull SMSMessage message,
133+
final @Nullable SMSSentListener listener,
134+
Context context) {
135+
if (listener == null || context == null)
136+
return null; //Doesn't make any sense to have a BroadcastReceiver if there is no listener or context
143137

144138
SMSSentBroadcastReceiver onSentReceiver = new SMSSentBroadcastReceiver(message, listener);
145139
String actionName = SENT_MESSAGE_INTENT_ACTION + (messageCounter++);
146-
context.get().registerReceiver(onSentReceiver, new IntentFilter(actionName));
147-
return PendingIntent.getBroadcast(context.get(), 0, new Intent(actionName), 0);
140+
context.registerReceiver(onSentReceiver, new IntentFilter(actionName));
141+
return PendingIntent.getBroadcast(context, 0, new Intent(actionName), 0);
148142
}
149143

150144
/**
@@ -153,26 +147,19 @@ private PendingIntent setupNewSentReceiver(final @NonNull SMSMessage message, fi
153147
*
154148
* @param message that will be sent
155149
* @param listener to call on broadcast received
150+
* @param context The context of the application used to setup the listener
156151
* @return a {@link PendingIntent} to be passed to SMSCore
157152
*/
158-
private PendingIntent setupNewDeliverReceiver(final @NonNull SMSMessage message, final @Nullable SMSDeliveredListener listener) {
159-
if (listener == null)
153+
private PendingIntent setupNewDeliverReceiver(final @NonNull SMSMessage message,
154+
final @Nullable SMSDeliveredListener listener,
155+
Context context) {
156+
if (listener == null || context == null)
160157
return null; //Doesn't make any sense to have a BroadcastReceiver if there is no listener
161158

162159
SMSDeliveredBroadcastReceiver onDeliveredReceiver = new SMSDeliveredBroadcastReceiver(message, listener);
163160
String actionName = DELIVERED_MESSAGE_INTENT_ACTION + (messageCounter++);
164-
context.get().registerReceiver(onDeliveredReceiver, new IntentFilter(actionName));
165-
return PendingIntent.getBroadcast(context.get(), 0, new Intent(actionName), 0);
166-
}
167-
168-
/**
169-
* Checks if the handler has been setup
170-
*
171-
* @throws IllegalStateException if the handler has not been setup
172-
*/
173-
private void checkSetup() {
174-
if (context == null)
175-
throw new IllegalStateException("You must call setup() first");
161+
context.registerReceiver(onDeliveredReceiver, new IntentFilter(actionName));
162+
return PendingIntent.getBroadcast(context, 0, new Intent(actionName), 0);
176163
}
177164

178165
/**
@@ -181,18 +168,19 @@ private void checkSetup() {
181168
*
182169
* @param receivedListenerClassName the listener called on message received
183170
* @param <T> the class type that extends {@link SMSReceivedServiceListener} to be called
171+
* @param context the context used to set the listener
184172
*/
185-
public <T extends SMSReceivedServiceListener> void setReceivedListener(Class<T> receivedListenerClassName) {
186-
checkSetup();
187-
PreferencesManager.setString(context.get(), SMSReceivedBroadcastReceiver.SERVICE_CLASS_PREFERENCES_KEY, receivedListenerClassName.toString());
173+
public <T extends SMSReceivedServiceListener> void setReceivedListener(Class<T> receivedListenerClassName, Context context) {
174+
PreferencesManager.setString(context, SMSReceivedBroadcastReceiver.SERVICE_CLASS_PREFERENCES_KEY, receivedListenerClassName.toString());
188175
}
189176

190177
/**
191178
* Unsubscribe the current {@link SMSReceivedServiceListener} from being called on message arrival
179+
*
180+
* @param context The context used to remove the listener
192181
*/
193-
public void removeReceivedListener() {
194-
checkSetup();
195-
PreferencesManager.removeValue(context.get(), SMSReceivedBroadcastReceiver.SERVICE_CLASS_PREFERENCES_KEY);
182+
public void removeReceivedListener(Context context) {
183+
PreferencesManager.removeValue(context, SMSReceivedBroadcastReceiver.SERVICE_CLASS_PREFERENCES_KEY);
196184
}
197185

198186
/**
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.eis.smslibrary;
2+
3+
import android.telephony.SmsManager;
4+
5+
import org.junit.Test;
6+
import org.junit.runner.RunWith;
7+
import org.mockito.runners.MockitoJUnitRunner;
8+
9+
import static org.junit.Assert.assertEquals;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.verify;
12+
13+
/**
14+
* Class used to test SMSManager using Mocks
15+
*
16+
* @author Marco Cognolato
17+
*/
18+
@RunWith(MockitoJUnitRunner.class)
19+
public class SMSHandlerTest {
20+
21+
private SmsManager managerMock = mock(SmsManager.class);
22+
23+
private final String PEER_TEXT = "+393423541601";
24+
private final String MESSAGE_TEXT = "valid text";
25+
private final String SENT_TEXT = (char) 0x02 + MESSAGE_TEXT;
26+
private final SMSPeer VALID_PEER = new SMSPeer(PEER_TEXT);
27+
private final SMSMessage VALID_MESSAGE = new SMSMessage(VALID_PEER, MESSAGE_TEXT);
28+
private SMSManager instance = SMSManager.getInstance();
29+
30+
@Test
31+
public void singletonInstance() {
32+
assertEquals(SMSManager.getInstance(), SMSManager.getInstance());
33+
}
34+
35+
@Test()
36+
public void validMessage_isSent() {
37+
SMSCore.setManager(managerMock);
38+
instance.sendMessage(VALID_MESSAGE);
39+
verify(managerMock).sendTextMessage(PEER_TEXT, null, SENT_TEXT, null, null);
40+
}
41+
42+
@Test(expected = NullPointerException.class)
43+
public void nullMessage_throws() {
44+
instance.sendMessage(null);
45+
}
46+
}

0 commit comments

Comments
 (0)