Skip to content

Commit 1491e06

Browse files
author
VinayGuthal
authored
Jobscheduler (#315)
* add scheduler * add scheduler log * switch branches * use static * update remote receiver * use max instead of min * add uploader to upload stuff * address comments * address nit * jobscheduler scheduler * make changes * revert idea change * make small changes * add tests * schedule test * add javadocs * add javadocs * address the comments * address comments * move back to immediate scheduler * Gjf
1 parent 020d6f4 commit 1491e06

File tree

12 files changed

+461
-8
lines changed

12 files changed

+461
-8
lines changed

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/TransportRuntime.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.support.annotation.VisibleForTesting;
2020
import com.google.android.datatransport.TransportFactory;
2121
import com.google.android.datatransport.runtime.scheduling.Scheduler;
22+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
2223
import com.google.android.datatransport.runtime.synchronization.SynchronizationGuard;
2324
import com.google.android.datatransport.runtime.time.Clock;
2425
import com.google.android.datatransport.runtime.time.Monotonic;
@@ -43,19 +44,22 @@ public class TransportRuntime implements TransportInternal {
4344
private final Clock uptimeClock;
4445
private final Scheduler scheduler;
4546
private final SynchronizationGuard guard;
47+
private final Uploader uploader;
4648

4749
@Inject
4850
TransportRuntime(
4951
BackendRegistry backendRegistry,
5052
@WallTime Clock eventClock,
5153
@Monotonic Clock uptimeClock,
5254
Scheduler scheduler,
53-
SynchronizationGuard guard) {
55+
SynchronizationGuard guard,
56+
Uploader uploader) {
5457
this.backendRegistry = backendRegistry;
5558
this.eventClock = eventClock;
5659
this.uptimeClock = uptimeClock;
5760
this.scheduler = scheduler;
5861
this.guard = guard;
62+
this.uploader = uploader;
5963
}
6064

6165
/**
@@ -98,6 +102,11 @@ public TransportFactory newFactory(String backendName) {
98102
return new TransportFactoryImpl(backendName, this);
99103
}
100104

105+
@RestrictTo(RestrictTo.Scope.LIBRARY)
106+
public Uploader getUploader() {
107+
return uploader;
108+
}
109+
101110
@Override
102111
public void send(SendRequest request) {
103112
scheduler.schedule(request.getBackendName(), convert(request));

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/TransportRuntimeModule.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@
1414

1515
package com.google.android.datatransport.runtime;
1616

17+
import android.content.Context;
18+
import android.os.Build;
1719
import com.google.android.datatransport.runtime.scheduling.ImmediateScheduler;
1820
import com.google.android.datatransport.runtime.scheduling.Scheduler;
21+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.AlarmManagerScheduler;
22+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoScheduler;
23+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
1924
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
2025
import com.google.android.datatransport.runtime.scheduling.persistence.SQLiteEventStore;
2126
import com.google.android.datatransport.runtime.synchronization.SynchronizationGuard;
@@ -50,7 +55,22 @@ static Clock uptimeClock() {
5055
}
5156

5257
@Provides
53-
static Scheduler scheduler(Executor executor, BackendRegistry registry) {
58+
static WorkScheduler workScheduler(
59+
Context context, EventStore eventStore, @WallTime Clock eventClock) {
60+
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
61+
return new JobInfoScheduler(context, eventStore, eventClock);
62+
} else {
63+
return new AlarmManagerScheduler(context, eventStore, eventClock);
64+
}
65+
}
66+
67+
@Provides
68+
static Scheduler scheduler(
69+
Executor executor,
70+
BackendRegistry registry,
71+
WorkScheduler workScheduler,
72+
EventStore eventStore,
73+
SynchronizationGuard guard) {
5474
return new ImmediateScheduler(executor, registry);
5575
}
5676

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/DefaultScheduler.java

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,68 @@
1414

1515
package com.google.android.datatransport.runtime.scheduling;
1616

17+
import com.google.android.datatransport.runtime.BackendRegistry;
1718
import com.google.android.datatransport.runtime.EventInternal;
19+
import com.google.android.datatransport.runtime.TransportBackend;
20+
import com.google.android.datatransport.runtime.TransportRuntime;
21+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
22+
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
23+
import com.google.android.datatransport.runtime.synchronization.SynchronizationGuard;
24+
import java.util.concurrent.Executor;
25+
import java.util.logging.Logger;
26+
import javax.inject.Inject;
1827

1928
/**
20-
* Placeholder for the eventual scheduler that will support persistence, retries, respect network
21-
* conditions and QoS.
29+
* Scheduler which persists the events, schedules the services which ultimately logs these events to
30+
* the corresponding backends. This respects network conditions and QoS.
2231
*/
2332
public class DefaultScheduler implements Scheduler {
33+
34+
private static final Logger LOGGER = Logger.getLogger(TransportRuntime.class.getName());
35+
private final WorkScheduler workScheduler;
36+
private final Executor executor;
37+
private final BackendRegistry backendRegistry;
38+
private final EventStore eventStore;
39+
private final SynchronizationGuard guard;
40+
private final int LOCK_TIME_OUT = 10000; // 10 seconds lock timeout
41+
42+
@Inject
43+
public DefaultScheduler(
44+
Executor executor,
45+
BackendRegistry backendRegistry,
46+
WorkScheduler workScheduler,
47+
EventStore eventStore,
48+
SynchronizationGuard guard) {
49+
this.executor = executor;
50+
this.backendRegistry = backendRegistry;
51+
this.workScheduler = workScheduler;
52+
this.eventStore = eventStore;
53+
this.guard = guard;
54+
}
55+
56+
/**
57+
* Schedules the events to be eventually logged to the backend.
58+
*
59+
* @param backendName The backend to which the event needs to be logged.
60+
* @param event The event itself which needs to be logged with additional information.
61+
*/
2462
@Override
2563
public void schedule(String backendName, EventInternal event) {
26-
// TODO: implement.
64+
executor.execute(
65+
() -> {
66+
TransportBackend transportBackend = backendRegistry.get(backendName);
67+
if (transportBackend == null) {
68+
LOGGER.warning(String.format("Logger backend '%s' is not registered", backendName));
69+
return;
70+
}
71+
EventInternal decoratedEvent = transportBackend.decorate(event);
72+
guard.runCriticalSection(
73+
LOCK_TIME_OUT,
74+
() -> {
75+
eventStore.persist(backendName, decoratedEvent);
76+
workScheduler.schedule(backendName, 0);
77+
return null;
78+
});
79+
});
2780
}
2881
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime.scheduling.jobscheduling;
16+
17+
import android.content.Context;
18+
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
19+
import com.google.android.datatransport.runtime.time.Clock;
20+
import javax.inject.Inject;
21+
22+
/**
23+
* Schedules the AlarmManager service based on the backendname. Used for Api levels 20 and below.
24+
*/
25+
public class AlarmManagerScheduler implements WorkScheduler {
26+
27+
private final Context context;
28+
29+
private final EventStore eventStore;
30+
31+
private final Clock clock;
32+
33+
@Inject
34+
public AlarmManagerScheduler(Context applicationContext, EventStore eventStore, Clock clock) {
35+
this.context = applicationContext;
36+
this.eventStore = eventStore;
37+
this.clock = clock;
38+
}
39+
40+
@Override
41+
public void schedule(String backendName, int attemptNumber) {}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime.scheduling.jobscheduling;
16+
17+
import android.app.job.JobInfo;
18+
import android.app.job.JobScheduler;
19+
import android.content.ComponentName;
20+
import android.content.Context;
21+
import android.os.Build;
22+
import android.os.PersistableBundle;
23+
import android.support.annotation.RequiresApi;
24+
import android.support.annotation.VisibleForTesting;
25+
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
26+
import com.google.android.datatransport.runtime.time.Clock;
27+
import java.util.zip.Adler32;
28+
import javax.inject.Inject;
29+
30+
/**
31+
* Schedules the service {@link JobInfoSchedulerService} based on the backendname. Used for Apis 21
32+
* and above.
33+
*/
34+
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
35+
public class JobInfoScheduler implements WorkScheduler {
36+
37+
private final Context context;
38+
39+
private final EventStore eventStore;
40+
41+
private final Clock clock;
42+
43+
private final int DELTA = 30000; // 30 seconds delta
44+
45+
@Inject
46+
public JobInfoScheduler(Context applicationContext, EventStore eventStore, Clock clock) {
47+
this.context = applicationContext;
48+
this.eventStore = eventStore;
49+
this.clock = clock;
50+
}
51+
52+
@VisibleForTesting
53+
int getJobId(String backendName) {
54+
Adler32 checksum = new Adler32();
55+
checksum.update(backendName.getBytes());
56+
return (int) checksum.getValue();
57+
}
58+
59+
private boolean isJobServiceOn(JobScheduler scheduler, int jobId) {
60+
for (JobInfo jobInfo : scheduler.getAllPendingJobs()) {
61+
if (jobInfo.getId() == jobId) {
62+
return true;
63+
}
64+
}
65+
return false;
66+
}
67+
68+
/**
69+
* Schedules the JobScheduler service.
70+
*
71+
* @param backendName The backend to where the events are logged.
72+
* @param attemptNumber Number of times the JobScheduler has tried to log for this backend.
73+
*/
74+
@Override
75+
public void schedule(String backendName, int attemptNumber) {
76+
ComponentName serviceComponent = new ComponentName(context, JobInfoSchedulerService.class);
77+
JobScheduler jobScheduler =
78+
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
79+
int jobId = getJobId(backendName);
80+
// Check if there exists a job scheduled for this backend name.
81+
if (isJobServiceOn(jobScheduler, jobId)) return;
82+
// Obtain the next available call time for the backend.
83+
long timeDiff = eventStore.getNextCallTime(backendName) - clock.getTime();
84+
// Schedule the build.
85+
PersistableBundle bundle = new PersistableBundle();
86+
bundle.putInt(SchedulerUtil.ATTEMPT_NUMBER, attemptNumber);
87+
bundle.putString(SchedulerUtil.BACKEND_NAME, backendName);
88+
JobInfo.Builder builder = new JobInfo.Builder(jobId, serviceComponent);
89+
builder.setMinimumLatency(
90+
clock.getTime()
91+
+ SchedulerUtil.getScheduleDelay(timeDiff, DELTA, attemptNumber)); // wait at least
92+
builder.setExtras(bundle);
93+
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
94+
jobScheduler.schedule(builder.build());
95+
}
96+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime.scheduling.jobscheduling;
16+
17+
import android.app.job.JobParameters;
18+
import android.app.job.JobService;
19+
import android.os.Build;
20+
import android.support.annotation.RequiresApi;
21+
import com.google.android.datatransport.runtime.TransportRuntime;
22+
23+
/** The service responsible for uploading information to the backend. */
24+
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
25+
public class JobInfoSchedulerService extends JobService {
26+
27+
@Override
28+
public boolean onStartJob(JobParameters params) {
29+
String backendName = params.getExtras().getString(SchedulerUtil.BACKEND_NAME);
30+
int attemptNumber = params.getExtras().getInt(SchedulerUtil.ATTEMPT_NUMBER);
31+
TransportRuntime.initialize(getApplicationContext());
32+
TransportRuntime.getInstance()
33+
.getUploader()
34+
.upload(backendName, attemptNumber, () -> this.jobFinished(params, false));
35+
return true;
36+
}
37+
38+
@Override
39+
public boolean onStopJob(JobParameters params) {
40+
return true;
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime.scheduling.jobscheduling;
16+
17+
/** Used by the schedulers for some basic constants and utility methods. */
18+
final class SchedulerUtil {
19+
20+
static final String ATTEMPT_NUMBER = "attemptNumber";
21+
22+
static final String BACKEND_NAME = "backendName";
23+
24+
static final String APPLICATION_BUNDLE_ID = "appBundleId";
25+
26+
static final int MAX_ALLOWED_TIME = 100000000;
27+
28+
private SchedulerUtil() {};
29+
30+
static long getScheduleDelay(long backendTimeDiff, int delta, int attemptNumber) {
31+
if (attemptNumber > 11) {
32+
return MAX_ALLOWED_TIME;
33+
}
34+
return Math.max((long) (Math.pow(2, attemptNumber)) * delta, backendTimeDiff);
35+
}
36+
}

0 commit comments

Comments
 (0)