Skip to content

Commit 657a478

Browse files
authored
Add client support for scheduling push notifications (#675)
* Add client support for push_time * Tests
1 parent e741077 commit 657a478

File tree

6 files changed

+146
-2
lines changed

6 files changed

+146
-2
lines changed

Parse/src/main/java/com/parse/ParsePush.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ private static void checkArgument(boolean expression, Object errorMessage) {
5555
private ParseQuery<ParseInstallation> query;
5656
private Long expirationTime;
5757
private Long expirationTimeInterval;
58+
private Long pushTime;
5859
private Boolean pushToIOS;
5960
private Boolean pushToAndroid;
6061
private JSONObject data;
@@ -72,6 +73,7 @@ public Builder(State state) {
7273
: new ParseQuery<>(new ParseQuery.State.Builder<ParseInstallation>(state.queryState()));
7374
this.expirationTime = state.expirationTime();
7475
this.expirationTimeInterval = state.expirationTimeInterval();
76+
this.pushTime = state.pushTime();
7577
this.pushToIOS = state.pushToIOS();
7678
this.pushToAndroid = state.pushToAndroid();
7779
// Since in state.build() we check data is not null, we do not need to check it again here.
@@ -96,6 +98,18 @@ public Builder expirationTimeInterval(Long expirationTimeInterval) {
9698
return this;
9799
}
98100

101+
public Builder pushTime(Long pushTime) {
102+
if (pushTime != null) {
103+
long now = System.currentTimeMillis() / 1000;
104+
long twoWeeks = 60*60*24*7*2;
105+
checkArgument(pushTime > now, "Scheduled push time can not be in the past");
106+
checkArgument(pushTime < now + twoWeeks, "Scheduled push time can not be more than " +
107+
"two weeks in the future");
108+
}
109+
this.pushTime = pushTime;
110+
return this;
111+
}
112+
99113
public Builder pushToIOS(Boolean pushToIOS) {
100114
checkArgument(query == null, "Cannot set push targets (i.e. setPushToAndroid or " +
101115
"setPushToIOS) when pushing to a query");
@@ -151,6 +165,7 @@ public State build() {
151165
private final ParseQuery.State<ParseInstallation> queryState;
152166
private final Long expirationTime;
153167
private final Long expirationTimeInterval;
168+
private final Long pushTime;
154169
private final Boolean pushToIOS;
155170
private final Boolean pushToAndroid;
156171
private final JSONObject data;
@@ -161,6 +176,7 @@ private State(Builder builder) {
161176
this.queryState = builder.query == null ? null : builder.query.getBuilder().build();
162177
this.expirationTime = builder.expirationTime;
163178
this.expirationTimeInterval = builder.expirationTimeInterval;
179+
this.pushTime = builder.pushTime;
164180
this.pushToIOS = builder.pushToIOS;
165181
this.pushToAndroid = builder.pushToAndroid;
166182
// Since in builder.build() we check data is not null, we do not need to check it again here.
@@ -189,6 +205,10 @@ public Long expirationTimeInterval() {
189205
return expirationTimeInterval;
190206
}
191207

208+
public Long pushTime() {
209+
return pushTime;
210+
}
211+
192212
public Boolean pushToIOS() {
193213
return pushToIOS;
194214
}
@@ -421,6 +441,14 @@ public void clearExpiration() {
421441
builder.expirationTimeInterval(null);
422442
}
423443

444+
/**
445+
* Sets a UNIX epoch timestamp at which this notification should be delivered, in seconds (UTC).
446+
* Scheduled time can not be in the past and must be at most two weeks in the future.
447+
*/
448+
public void setPushTime(long time) {
449+
builder.pushTime(time);
450+
}
451+
424452
/**
425453
* Set whether this push notification will go to iOS devices.
426454
* <p/>

Parse/src/main/java/com/parse/ParsePushController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public Task<Void> sendInBackground(ParsePush.State state, String sessionToken) {
4343
}
4444
}
4545
return ParseRESTPushCommand.sendPushCommand(state.queryState(), state.channelSet(), deviceType,
46-
state.expirationTime(), state.expirationTimeInterval(), state.data(), sessionToken);
46+
state.expirationTime(), state.expirationTimeInterval(), state.pushTime(), state.data(),
47+
sessionToken);
4748
}
4849
}

Parse/src/main/java/com/parse/ParseRESTPushCommand.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
/* package */ final static String KEY_DEVICE_TYPE = "deviceType";
2424
/* package */ final static String KEY_EXPIRATION_TIME = "expiration_time";
2525
/* package */ final static String KEY_EXPIRATION_INTERVAL = "expiration_interval";
26+
/* package */ final static String KEY_PUSH_TIME = "push_time";
2627
/* package */ final static String KEY_DATA = "data";
2728

2829
public ParseRESTPushCommand(
@@ -35,7 +36,7 @@ public ParseRESTPushCommand(
3536

3637
public static ParseRESTPushCommand sendPushCommand(ParseQuery.State<ParseInstallation> query,
3738
Set<String> targetChannels, String targetDeviceType, Long expirationTime,
38-
Long expirationInterval, JSONObject payload, String sessionToken) {
39+
Long expirationInterval, Long pushTime, JSONObject payload, String sessionToken) {
3940
JSONObject parameters = new JSONObject();
4041
try {
4142
if (targetChannels != null) {
@@ -63,9 +64,14 @@ public static ParseRESTPushCommand sendPushCommand(ParseQuery.State<ParseInstall
6364
parameters.put(KEY_EXPIRATION_INTERVAL, expirationInterval);
6465
}
6566

67+
if (pushTime != null) {
68+
parameters.put(KEY_PUSH_TIME, pushTime);
69+
}
70+
6671
if (payload != null) {
6772
parameters.put(KEY_DATA, payload);
6873
}
74+
6975
} catch (JSONException e) {
7076
throw new RuntimeException(e);
7177
}

Parse/src/test/java/com/parse/ParsePushControllerTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public void testBuildRESTSendPushCommandWithChannelSet() throws Exception {
8383

8484
// Verify command
8585
JSONObject jsonParameters = pushCommand.jsonParameters;
86+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_PUSH_TIME));
8687
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_TIME));
8788
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_INTERVAL));
8889
// Verify device type and query
@@ -113,6 +114,7 @@ public void testBuildRESTSendPushCommandWithExpirationTime() throws Exception {
113114

114115
// Verify command
115116
JSONObject jsonParameters = pushCommand.jsonParameters;
117+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_PUSH_TIME));
116118
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_INTERVAL));
117119
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_CHANNELS));
118120
// Verify device type and query
@@ -123,6 +125,36 @@ public void testBuildRESTSendPushCommandWithExpirationTime() throws Exception {
123125
assertEquals(1400000000, jsonParameters.getLong(ParseRESTPushCommand.KEY_EXPIRATION_TIME));
124126
}
125127

128+
@Test
129+
public void testBuildRESTSendPushCommandWithPushTime() throws Exception {
130+
ParseHttpClient restClient = mock(ParseHttpClient.class);
131+
ParsePushController controller = new ParsePushController(restClient);
132+
133+
// Build PushState
134+
JSONObject data = new JSONObject();
135+
data.put(ParsePush.KEY_DATA_MESSAGE, "hello world");
136+
long pushTime = System.currentTimeMillis() / 1000 + 1000;
137+
ParsePush.State state = new ParsePush.State.Builder()
138+
.data(data)
139+
.pushTime(pushTime)
140+
.build();
141+
142+
// Build command
143+
ParseRESTCommand pushCommand = controller.buildRESTSendPushCommand(state, "sessionToken");
144+
145+
// Verify command
146+
JSONObject jsonParameters = pushCommand.jsonParameters;
147+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_TIME));
148+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_INTERVAL));
149+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_CHANNELS));
150+
// Verify device type and query
151+
assertEquals("{}", jsonParameters.get(ParseRESTPushCommand.KEY_WHERE).toString());
152+
assertEquals("hello world",
153+
jsonParameters.getJSONObject(ParseRESTPushCommand.KEY_DATA)
154+
.getString(ParsePush.KEY_DATA_MESSAGE));
155+
assertEquals(pushTime, jsonParameters.getLong(ParseRESTPushCommand.KEY_PUSH_TIME));
156+
}
157+
126158
@Test
127159
public void testBuildRESTSendPushCommandWithExpirationTimeInterval() throws Exception {
128160
ParseHttpClient restClient = mock(ParseHttpClient.class);
@@ -141,6 +173,7 @@ public void testBuildRESTSendPushCommandWithExpirationTimeInterval() throws Exce
141173

142174
// Verify command
143175
JSONObject jsonParameters = pushCommand.jsonParameters;
176+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_PUSH_TIME));
144177
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_TIME));
145178
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_CHANNELS));
146179
// Verify device type and query
@@ -172,6 +205,7 @@ public void testBuildRESTSendPushCommandWithQuery() throws Exception {
172205

173206
// Verify command
174207
JSONObject jsonParameters = pushCommand.jsonParameters;
208+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_PUSH_TIME));
175209
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_TIME));
176210
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_INTERVAL));
177211
assertFalse(jsonParameters.getJSONObject(ParseRESTPushCommand.KEY_WHERE)
@@ -206,6 +240,7 @@ public void testBuildRESTSendPushCommandWithPushToAndroid() throws Exception {
206240

207241
// Verify command
208242
JSONObject jsonParameters = pushCommand.jsonParameters;
243+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_PUSH_TIME));
209244
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_TIME));
210245
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_INTERVAL));
211246
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_CHANNELS));
@@ -235,6 +270,7 @@ public void testBuildRESTSendPushCommandWithPushToIOS() throws Exception {
235270

236271
// Verify command
237272
JSONObject jsonParameters = pushCommand.jsonParameters;
273+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_PUSH_TIME));
238274
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_TIME));
239275
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_INTERVAL));
240276
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_CHANNELS));
@@ -265,6 +301,7 @@ public void testBuildRESTSendPushCommandWithPushToIOSAndAndroid() throws Excepti
265301

266302
// Verify command
267303
JSONObject jsonParameters = pushCommand.jsonParameters;
304+
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_PUSH_TIME));
268305
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_TIME));
269306
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_EXPIRATION_INTERVAL));
270307
assertFalse(jsonParameters.has(ParseRESTPushCommand.KEY_CHANNELS));

Parse/src/test/java/com/parse/ParsePushStateTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public void testDefaultsWithData() throws Exception {
5454

5555
assertEquals(null, state.expirationTime());
5656
assertEquals(null, state.expirationTimeInterval());
57+
assertEquals(null, state.pushTime());
5758
assertEquals(null, state.channelSet());
5859
JSONAssert.assertEquals(data, state.data(), JSONCompareMode.NON_EXTENSIBLE);
5960
assertEquals(null, state.pushToAndroid());
@@ -68,6 +69,7 @@ public void testCopy() throws JSONException {
6869
ParsePush.State state = mock(ParsePush.State.class);
6970
when(state.expirationTime()).thenReturn(1L);
7071
when(state.expirationTimeInterval()).thenReturn(2L);
72+
when(state.pushTime()).thenReturn(3L);
7173
Set channelSet = Sets.newSet("one", "two");
7274
when(state.channelSet()).thenReturn(channelSet);
7375
JSONObject data = new JSONObject();
@@ -82,6 +84,7 @@ public void testCopy() throws JSONException {
8284
ParsePush.State copy = new ParsePush.State.Builder(state).build();
8385
assertSame(1L, copy.expirationTime());
8486
assertSame(2L, copy.expirationTimeInterval());
87+
assertSame(3L, copy.pushTime());
8588
Set channelSetCopy = copy.channelSet();
8689
assertNotSame(channelSet, channelSetCopy);
8790
assertTrue(channelSetCopy.size() == 2 && channelSetCopy.contains("one"));
@@ -151,6 +154,55 @@ public void testExpirationTimeIntervalNormalInterval() {
151154

152155
//endregion
153156

157+
//region testPushTime
158+
159+
@Test
160+
public void testPushTimeNullTime() {
161+
ParsePush.State.Builder builder = new ParsePush.State.Builder();
162+
163+
ParsePush.State state = builder
164+
.pushTime(null)
165+
.data(new JSONObject())
166+
.build();
167+
168+
assertEquals(null, state.pushTime());
169+
}
170+
171+
@Test
172+
public void testPushTimeNormalTime() {
173+
ParsePush.State.Builder builder = new ParsePush.State.Builder();
174+
175+
long time = System.currentTimeMillis() / 1000 + 1000;
176+
ParsePush.State state = builder
177+
.pushTime(time)
178+
.data(new JSONObject())
179+
.build();
180+
181+
assertEquals(time, state.pushTime().longValue());
182+
}
183+
184+
@Test(expected = IllegalArgumentException.class)
185+
public void testPushTimeInThePast() {
186+
ParsePush.State.Builder builder = new ParsePush.State.Builder();
187+
188+
ParsePush.State state = builder
189+
.pushTime(System.currentTimeMillis() / 1000 - 1000)
190+
.data(new JSONObject())
191+
.build();
192+
}
193+
194+
@Test(expected = IllegalArgumentException.class)
195+
public void testPushTimeTwoWeeksFromNow() {
196+
ParsePush.State.Builder builder = new ParsePush.State.Builder();
197+
198+
ParsePush.State state = builder
199+
.pushTime(System.currentTimeMillis() / 1000 + 60*60*24*7*3)
200+
.data(new JSONObject())
201+
.build();
202+
}
203+
204+
//endregion
205+
154206
//region testChannelSet
155207

156208
@Test(expected = IllegalArgumentException.class)

Parse/src/test/java/com/parse/ParsePushTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,26 @@ public void testClearExpiration() {
206206

207207
//endregion
208208

209+
//region testSetPushTime
210+
211+
// We only test a basic case here to make sure logic in ParsePush is correct, more comprehensive
212+
// builder test cases should be in ParsePushState test
213+
@Test
214+
public void testSetPushTime() throws Exception {
215+
ParsePush push = new ParsePush();
216+
long time = System.currentTimeMillis() / 1000 + 1000;
217+
push.setPushTime(time);
218+
219+
// Right now it is hard for us to test a builder, so we build a state to test the builder is
220+
// set correctly
221+
// We have to set message otherwise build() will throw an exception
222+
push.setMessage("message");
223+
ParsePush.State state = push.builder.build();
224+
assertEquals(time, state.pushTime().longValue());
225+
}
226+
227+
//endregion
228+
209229
//region testSetPushToIOS
210230

211231
// We only test a basic case here to make sure logic in ParsePush is correct, more comprehensive

0 commit comments

Comments
 (0)