From 1f1659692917be8b405015e22450dc14ccfc04bf Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Wed, 28 Aug 2024 12:52:29 -0500 Subject: [PATCH 1/9] Initial implementation --- .../runtime/scheduling/SchedulingModule.java | 5 +- .../jobscheduling/WorkManagerScheduler.java | 128 ++++++++++++++++++ .../WorkManagerSchedulerWorker.java | 58 ++++++++ .../transport-runtime.gradle | 1 + 4 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java create mode 100644 transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerSchedulerWorker.java diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/SchedulingModule.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/SchedulingModule.java index 20ef1dd0f90..94059f615de 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/SchedulingModule.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/SchedulingModule.java @@ -19,6 +19,7 @@ import com.google.android.datatransport.runtime.scheduling.jobscheduling.AlarmManagerScheduler; import com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoScheduler; import com.google.android.datatransport.runtime.scheduling.jobscheduling.SchedulerConfig; +import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkManagerScheduler; import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler; import com.google.android.datatransport.runtime.scheduling.persistence.EventStore; import com.google.android.datatransport.runtime.time.Clock; @@ -32,7 +33,9 @@ public abstract class SchedulingModule { @Provides static WorkScheduler workScheduler( Context context, EventStore eventStore, SchedulerConfig config, @Monotonic Clock clock) { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + return new WorkManagerScheduler(context, eventStore, config); + } else if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return new JobInfoScheduler(context, eventStore, config); } else { return new AlarmManagerScheduler(context, eventStore, clock, config); diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java new file mode 100644 index 00000000000..a6b869b09b8 --- /dev/null +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java @@ -0,0 +1,128 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.android.datatransport.runtime.scheduling.jobscheduling; + +import static android.util.Base64.DEFAULT; +import static android.util.Base64.encodeToString; + +import android.content.Context; +import androidx.annotation.VisibleForTesting; +import androidx.work.Data; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; +import androidx.work.WorkRequest; +import com.google.android.datatransport.runtime.TransportContext; +import com.google.android.datatransport.runtime.logging.Logging; +import com.google.android.datatransport.runtime.scheduling.persistence.EventStore; +import com.google.android.datatransport.runtime.util.PriorityMapping; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.zip.Adler32; + +public class WorkManagerScheduler implements WorkScheduler { + private static final String LOG_TAG = "JobInfoScheduler"; + + static final String ATTEMPT_NUMBER = "attemptNumber"; + static final String BACKEND_NAME = "backendName"; + static final String EVENT_PRIORITY = "priority"; + static final String EXTRAS = "extras"; + private static final Map JOBS = new HashMap<>(); + private final Context context; + + private final EventStore eventStore; + + private final SchedulerConfig config; + + public WorkManagerScheduler( + Context applicationContext, EventStore eventStore, SchedulerConfig config) { + this.context = applicationContext; + this.eventStore = eventStore; + this.config = config; + } + + @VisibleForTesting + int getJobId(TransportContext transportContext) { + Adler32 checksum = new Adler32(); + checksum.update(context.getPackageName().getBytes(Charset.forName("UTF-8"))); + checksum.update(transportContext.getBackendName().getBytes(Charset.forName("UTF-8"))); + checksum.update( + ByteBuffer.allocate(4) + .putInt(PriorityMapping.toInt(transportContext.getPriority())) + .array()); + if (transportContext.getExtras() != null) { + checksum.update(transportContext.getExtras()); + } + return (int) checksum.getValue(); + } + + @Override + public void schedule(TransportContext transportContext, int attemptNumber) { + schedule(transportContext, attemptNumber, false); + } + + @Override + public void schedule(TransportContext transportContext, int attemptNumber, boolean force) { + WorkManager manager = WorkManager.getInstance(context); + + int jobId = getJobId(transportContext); + if (!force && JOBS.containsKey(jobId)) { + try { + if (!manager.getWorkInfoById(JOBS.get(jobId)).get().getState().isFinished()) { + Logging.d( + LOG_TAG, + "Upload for context %s is already scheduled. Returning...", + transportContext); + return; + } + } catch (Exception e) { + } + } + + Data.Builder dataBuilder = new Data.Builder(); + dataBuilder.putInt(ATTEMPT_NUMBER, attemptNumber); + dataBuilder.putString(BACKEND_NAME, transportContext.getBackendName()); + dataBuilder.putInt(EVENT_PRIORITY, PriorityMapping.toInt(transportContext.getPriority())); + if (transportContext.getExtras() != null) { + dataBuilder.putString(EXTRAS, encodeToString(transportContext.getExtras(), DEFAULT)); + } + + long backendTime = eventStore.getNextCallTime(transportContext); + boolean hasPendingEvents = force && eventStore.hasPendingEventsFor(transportContext); + + long scheduleDelay = + config.getScheduleDelay( + transportContext.getPriority(), backendTime, attemptNumber, hasPendingEvents); + + Logging.d( + LOG_TAG, + "Scheduling upload for context %s in %dms(Backend next call timestamp %d). Attempt %d", + transportContext, + scheduleDelay, + backendTime, + attemptNumber); + + WorkRequest request = + new OneTimeWorkRequest.Builder(WorkManagerSchedulerWorker.class) + .setInitialDelay(scheduleDelay, TimeUnit.MILLISECONDS) + .setInputData(dataBuilder.build()) + .build(); + JOBS.put(jobId, request.getId()); + manager.enqueue(request); + } +} diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerSchedulerWorker.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerSchedulerWorker.java new file mode 100644 index 00000000000..8f681be6b5c --- /dev/null +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerSchedulerWorker.java @@ -0,0 +1,58 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.android.datatransport.runtime.scheduling.jobscheduling; + +import android.content.Context; +import android.util.Base64; +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; +import com.google.android.datatransport.runtime.TransportContext; +import com.google.android.datatransport.runtime.TransportRuntime; +import com.google.android.datatransport.runtime.util.PriorityMapping; + +public class WorkManagerSchedulerWorker extends Worker { + private WorkerParameters params; + + public WorkManagerSchedulerWorker( + @NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + this.params = workerParams; + } + + @NonNull + @Override + public Result doWork() { + String backendName = params.getInputData().getString(JobInfoScheduler.BACKEND_NAME); + String extras = params.getInputData().getString(JobInfoScheduler.EXTRAS); + + int priority = params.getInputData().getInt(JobInfoScheduler.EVENT_PRIORITY, 0); + int attemptNumber = params.getInputData().getInt(JobInfoScheduler.ATTEMPT_NUMBER, 0); + TransportRuntime.initialize(getApplicationContext()); + TransportContext.Builder transportContext = + TransportContext.builder() + .setBackendName(backendName) + .setPriority(PriorityMapping.valueOf(priority)); + + if (extras != null) { + transportContext.setExtras(Base64.decode(extras, Base64.DEFAULT)); + } + + TransportRuntime.getInstance() + .getUploader() + .upload(transportContext.build(), attemptNumber, () -> {}); + return Result.success(); + } +} diff --git a/transport/transport-runtime/transport-runtime.gradle b/transport/transport-runtime/transport-runtime.gradle index 0eb853e98c8..ecc46a4b2a0 100644 --- a/transport/transport-runtime/transport-runtime.gradle +++ b/transport/transport-runtime/transport-runtime.gradle @@ -110,6 +110,7 @@ dependencies { api "com.google.firebase:firebase-encoders-proto:16.0.0" implementation 'androidx.annotation:annotation:1.3.0' + implementation 'androidx.work:work-runtime:2.9.1' implementation 'javax.inject:javax.inject:1' compileOnly "com.google.auto.value:auto-value-annotations:1.6.6" From 1cb0ed4649de696dcdbdc99e7a4afc0016b2f92f Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Fri, 30 Aug 2024 14:42:14 -0500 Subject: [PATCH 2/9] Requested fixes --- .../jobscheduling/JobInfoScheduler.java | 24 ++-------------- .../jobscheduling/WorkManagerScheduler.java | 28 ++++--------------- .../jobscheduling/WorkScheduler.java | 21 ++++++++++++++ .../jobscheduling/JobInfoSchedulerTest.java | 16 +++++------ 4 files changed, 38 insertions(+), 51 deletions(-) diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/JobInfoScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/JobInfoScheduler.java index 14fc99f043a..965e4efc6de 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/JobInfoScheduler.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/JobInfoScheduler.java @@ -14,7 +14,8 @@ package com.google.android.datatransport.runtime.scheduling.jobscheduling; -import static android.util.Base64.*; +import static android.util.Base64.DEFAULT; +import static android.util.Base64.encodeToString; import android.app.job.JobInfo; import android.app.job.JobScheduler; @@ -23,14 +24,10 @@ import android.os.Build; import android.os.PersistableBundle; import androidx.annotation.RequiresApi; -import androidx.annotation.VisibleForTesting; import com.google.android.datatransport.runtime.TransportContext; import com.google.android.datatransport.runtime.logging.Logging; import com.google.android.datatransport.runtime.scheduling.persistence.EventStore; import com.google.android.datatransport.runtime.util.PriorityMapping; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.zip.Adler32; /** * Schedules the service {@link JobInfoSchedulerService} based on the backendname. Used for Apis 21 @@ -58,21 +55,6 @@ public JobInfoScheduler( this.config = config; } - @VisibleForTesting - int getJobId(TransportContext transportContext) { - Adler32 checksum = new Adler32(); - checksum.update(context.getPackageName().getBytes(Charset.forName("UTF-8"))); - checksum.update(transportContext.getBackendName().getBytes(Charset.forName("UTF-8"))); - checksum.update( - ByteBuffer.allocate(4) - .putInt(PriorityMapping.toInt(transportContext.getPriority())) - .array()); - if (transportContext.getExtras() != null) { - checksum.update(transportContext.getExtras()); - } - return (int) checksum.getValue(); - } - private boolean isJobServiceOn(JobScheduler scheduler, int jobId, int attemptNumber) { for (JobInfo jobInfo : scheduler.getAllPendingJobs()) { int existingAttemptNumber = jobInfo.getExtras().getInt(ATTEMPT_NUMBER); @@ -106,7 +88,7 @@ public void schedule(TransportContext transportContext, int attemptNumber, boole ComponentName serviceComponent = new ComponentName(context, JobInfoSchedulerService.class); JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - int jobId = getJobId(transportContext); + int jobId = WorkScheduler.getJobId(context, transportContext); // Check if there exists a job scheduled for this backend name. if (!force && isJobServiceOn(jobScheduler, jobId, attemptNumber)) { Logging.d( diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java index a6b869b09b8..091a75445b7 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java @@ -18,7 +18,8 @@ import static android.util.Base64.encodeToString; import android.content.Context; -import androidx.annotation.VisibleForTesting; +import android.os.Build; +import androidx.annotation.RequiresApi; import androidx.work.Data; import androidx.work.OneTimeWorkRequest; import androidx.work.WorkManager; @@ -27,14 +28,12 @@ import com.google.android.datatransport.runtime.logging.Logging; import com.google.android.datatransport.runtime.scheduling.persistence.EventStore; import com.google.android.datatransport.runtime.util.PriorityMapping; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; -import java.util.zip.Adler32; +@RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public class WorkManagerScheduler implements WorkScheduler { private static final String LOG_TAG = "JobInfoScheduler"; @@ -42,7 +41,7 @@ public class WorkManagerScheduler implements WorkScheduler { static final String BACKEND_NAME = "backendName"; static final String EVENT_PRIORITY = "priority"; static final String EXTRAS = "extras"; - private static final Map JOBS = new HashMap<>(); + private static final Map JOBS = new ConcurrentHashMap<>(); private final Context context; private final EventStore eventStore; @@ -56,21 +55,6 @@ public WorkManagerScheduler( this.config = config; } - @VisibleForTesting - int getJobId(TransportContext transportContext) { - Adler32 checksum = new Adler32(); - checksum.update(context.getPackageName().getBytes(Charset.forName("UTF-8"))); - checksum.update(transportContext.getBackendName().getBytes(Charset.forName("UTF-8"))); - checksum.update( - ByteBuffer.allocate(4) - .putInt(PriorityMapping.toInt(transportContext.getPriority())) - .array()); - if (transportContext.getExtras() != null) { - checksum.update(transportContext.getExtras()); - } - return (int) checksum.getValue(); - } - @Override public void schedule(TransportContext transportContext, int attemptNumber) { schedule(transportContext, attemptNumber, false); @@ -80,7 +64,7 @@ public void schedule(TransportContext transportContext, int attemptNumber) { public void schedule(TransportContext transportContext, int attemptNumber, boolean force) { WorkManager manager = WorkManager.getInstance(context); - int jobId = getJobId(transportContext); + int jobId = WorkScheduler.getJobId(context, transportContext); if (!force && JOBS.containsKey(jobId)) { try { if (!manager.getWorkInfoById(JOBS.get(jobId)).get().getState().isFinished()) { diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkScheduler.java index 08f15ae623c..0edfe6ef5f7 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkScheduler.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkScheduler.java @@ -14,11 +14,32 @@ package com.google.android.datatransport.runtime.scheduling.jobscheduling; +import android.content.Context; +import androidx.annotation.VisibleForTesting; import com.google.android.datatransport.runtime.TransportContext; +import com.google.android.datatransport.runtime.util.PriorityMapping; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.zip.Adler32; /** Schedules the services to be able to eventually log events to their respective backends. */ public interface WorkScheduler { void schedule(TransportContext transportContext, int attemptNumber); void schedule(TransportContext transportContext, int attemptNumber, boolean force); + + @VisibleForTesting + static int getJobId(Context context, TransportContext transportContext) { + Adler32 checksum = new Adler32(); + checksum.update(context.getPackageName().getBytes(Charset.forName("UTF-8"))); + checksum.update(transportContext.getBackendName().getBytes(Charset.forName("UTF-8"))); + checksum.update( + ByteBuffer.allocate(4) + .putInt(PriorityMapping.toInt(transportContext.getPriority())) + .array()); + if (transportContext.getExtras() != null) { + checksum.update(transportContext.getExtras()); + } + return (int) checksum.getValue(); + } } diff --git a/transport/transport-runtime/src/test/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/JobInfoSchedulerTest.java b/transport/transport-runtime/src/test/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/JobInfoSchedulerTest.java index 197daa2747c..a2ea0087f26 100644 --- a/transport/transport-runtime/src/test/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/JobInfoSchedulerTest.java +++ b/transport/transport-runtime/src/test/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/JobInfoSchedulerTest.java @@ -61,7 +61,7 @@ public class JobInfoSchedulerTest { public void schedule_secondAttemptThenForce() { store.recordNextCallTime(TRANSPORT_CONTEXT, 5); scheduler.schedule(TRANSPORT_CONTEXT, 2); - int jobId = scheduler.getJobId(TRANSPORT_CONTEXT); + int jobId = WorkScheduler.getJobId(context, TRANSPORT_CONTEXT); assertThat(jobScheduler.getAllPendingJobs()).isNotEmpty(); assertThat(jobScheduler.getAllPendingJobs().size()).isEqualTo(1); JobInfo jobInfo = jobScheduler.getAllPendingJobs().get(0); @@ -86,7 +86,7 @@ public void schedule_secondAttemptThenForce() { public void schedule_longWaitTimeFirstAttempt() { store.recordNextCallTime(TRANSPORT_CONTEXT, 1000000); scheduler.schedule(TRANSPORT_CONTEXT, 1); - int jobId = scheduler.getJobId(TRANSPORT_CONTEXT); + int jobId = WorkScheduler.getJobId(context, TRANSPORT_CONTEXT); assertThat(jobScheduler.getAllPendingJobs()).isNotEmpty(); assertThat(jobScheduler.getAllPendingJobs().size()).isEqualTo(1); JobInfo jobInfo = jobScheduler.getAllPendingJobs().get(0); @@ -101,7 +101,7 @@ public void schedule_longWaitTimeFirstAttempt() { @Test public void schedule_noTimeRecordedForBackend() { scheduler.schedule(TRANSPORT_CONTEXT, 1); - int jobId = scheduler.getJobId(TRANSPORT_CONTEXT); + int jobId = WorkScheduler.getJobId(context, TRANSPORT_CONTEXT); assertThat(jobScheduler.getAllPendingJobs()).isNotEmpty(); assertThat(jobScheduler.getAllPendingJobs().size()).isEqualTo(1); JobInfo jobInfo = jobScheduler.getAllPendingJobs().get(0); @@ -117,7 +117,7 @@ public void schedule_noTimeRecordedForBackend() { public void schedule_smallWaitTImeFirstAttempt() { store.recordNextCallTime(TRANSPORT_CONTEXT, 5); scheduler.schedule(TRANSPORT_CONTEXT, 1); - int jobId = scheduler.getJobId(TRANSPORT_CONTEXT); + int jobId = WorkScheduler.getJobId(context, TRANSPORT_CONTEXT); assertThat(jobScheduler.getAllPendingJobs()).isNotEmpty(); assertThat(jobScheduler.getAllPendingJobs().size()).isEqualTo(1); JobInfo jobInfo = jobScheduler.getAllPendingJobs().get(0); @@ -133,7 +133,7 @@ public void schedule_smallWaitTImeFirstAttempt() { public void schedule_longWaitTimeTenthAttempt() { store.recordNextCallTime(TRANSPORT_CONTEXT, 1000000); scheduler.schedule(TRANSPORT_CONTEXT, 10); - int jobId = scheduler.getJobId(TRANSPORT_CONTEXT); + int jobId = WorkScheduler.getJobId(context, TRANSPORT_CONTEXT); assertThat(jobScheduler.getAllPendingJobs()).isNotEmpty(); assertThat(jobScheduler.getAllPendingJobs().size()).isEqualTo(1); JobInfo jobInfo = jobScheduler.getAllPendingJobs().get(0); @@ -148,7 +148,7 @@ public void schedule_longWaitTimeTenthAttempt() { @Test public void schedule_twoJobs() { store.recordNextCallTime(TRANSPORT_CONTEXT, 5); - int jobId = scheduler.getJobId(TRANSPORT_CONTEXT); + int jobId = WorkScheduler.getJobId(context, TRANSPORT_CONTEXT); // Schedule first job scheduler.schedule(TRANSPORT_CONTEXT, 1); assertThat(jobScheduler.getAllPendingJobs()).isNotEmpty(); @@ -221,8 +221,8 @@ public void schedule_smallWaitTimeFirstAttempt_multiplePriorities() { store.recordNextCallTime(TRANSPORT_CONTEXT, 5); scheduler.schedule(TRANSPORT_CONTEXT, 1); scheduler.schedule(UNMETERED_TRANSPORT_CONTEXT, 1); - int jobId1 = scheduler.getJobId(TRANSPORT_CONTEXT); - int jobId2 = scheduler.getJobId(UNMETERED_TRANSPORT_CONTEXT); + int jobId1 = WorkScheduler.getJobId(context, TRANSPORT_CONTEXT); + int jobId2 = WorkScheduler.getJobId(context, UNMETERED_TRANSPORT_CONTEXT); assertThat(jobScheduler.getAllPendingJobs()).isNotEmpty(); assertThat(jobScheduler.getAllPendingJobs().size()).isEqualTo(2); From ca0644aa0d72b58fae8adb83ae11a3d81365abb2 Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Fri, 30 Aug 2024 14:43:40 -0500 Subject: [PATCH 3/9] WorkManagerScheduler tag --- .../runtime/scheduling/jobscheduling/WorkManagerScheduler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java index 091a75445b7..0763d0e4478 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java @@ -35,7 +35,7 @@ @RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public class WorkManagerScheduler implements WorkScheduler { - private static final String LOG_TAG = "JobInfoScheduler"; + private static final String LOG_TAG = "WorkManagerScheduler"; static final String ATTEMPT_NUMBER = "attemptNumber"; static final String BACKEND_NAME = "backendName"; From 7c99278df024d74f55802853b07e6e9ca5a5db97 Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Fri, 30 Aug 2024 14:47:41 -0500 Subject: [PATCH 4/9] Worker changes --- .../jobscheduling/WorkManagerSchedulerWorker.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerSchedulerWorker.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerSchedulerWorker.java index 8f681be6b5c..d739a4e0379 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerSchedulerWorker.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerSchedulerWorker.java @@ -15,31 +15,34 @@ package com.google.android.datatransport.runtime.scheduling.jobscheduling; import android.content.Context; +import android.os.Build; import android.util.Base64; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.work.Data; import androidx.work.Worker; import androidx.work.WorkerParameters; import com.google.android.datatransport.runtime.TransportContext; import com.google.android.datatransport.runtime.TransportRuntime; import com.google.android.datatransport.runtime.util.PriorityMapping; +@RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public class WorkManagerSchedulerWorker extends Worker { - private WorkerParameters params; public WorkManagerSchedulerWorker( @NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); - this.params = workerParams; } @NonNull @Override public Result doWork() { - String backendName = params.getInputData().getString(JobInfoScheduler.BACKEND_NAME); - String extras = params.getInputData().getString(JobInfoScheduler.EXTRAS); + Data data = getInputData(); + String backendName = data.getString(JobInfoScheduler.BACKEND_NAME); + String extras = data.getString(JobInfoScheduler.EXTRAS); - int priority = params.getInputData().getInt(JobInfoScheduler.EVENT_PRIORITY, 0); - int attemptNumber = params.getInputData().getInt(JobInfoScheduler.ATTEMPT_NUMBER, 0); + int priority = data.getInt(JobInfoScheduler.EVENT_PRIORITY, 0); + int attemptNumber = data.getInt(JobInfoScheduler.ATTEMPT_NUMBER, 0); TransportRuntime.initialize(getApplicationContext()); TransportContext.Builder transportContext = TransportContext.builder() From 3a4cb1cc61cf8869282cfecdf3404a5ad47d5deb Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Fri, 30 Aug 2024 15:03:32 -0500 Subject: [PATCH 5/9] Migrate to tags --- .../jobscheduling/WorkManagerScheduler.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java index 0763d0e4478..fe6eaa2f636 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java @@ -22,6 +22,7 @@ import androidx.annotation.RequiresApi; import androidx.work.Data; import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkInfo; import androidx.work.WorkManager; import androidx.work.WorkRequest; import com.google.android.datatransport.runtime.TransportContext; @@ -41,7 +42,6 @@ public class WorkManagerScheduler implements WorkScheduler { static final String BACKEND_NAME = "backendName"; static final String EVENT_PRIORITY = "priority"; static final String EXTRAS = "extras"; - private static final Map JOBS = new ConcurrentHashMap<>(); private final Context context; private final EventStore eventStore; @@ -65,14 +65,16 @@ public void schedule(TransportContext transportContext, int attemptNumber, boole WorkManager manager = WorkManager.getInstance(context); int jobId = WorkScheduler.getJobId(context, transportContext); - if (!force && JOBS.containsKey(jobId)) { + if (!force) { try { - if (!manager.getWorkInfoById(JOBS.get(jobId)).get().getState().isFinished()) { - Logging.d( - LOG_TAG, - "Upload for context %s is already scheduled. Returning...", - transportContext); - return; + for (WorkInfo info : manager.getWorkInfosByTag("transport-" + jobId).get()) { + if (!info.getState().isFinished()) { + Logging.d( + LOG_TAG, + "Upload for context %s is already scheduled. Returning...", + transportContext); + return; + } } } catch (Exception e) { } @@ -105,8 +107,8 @@ public void schedule(TransportContext transportContext, int attemptNumber, boole new OneTimeWorkRequest.Builder(WorkManagerSchedulerWorker.class) .setInitialDelay(scheduleDelay, TimeUnit.MILLISECONDS) .setInputData(dataBuilder.build()) + .addTag("transport-" + jobId) .build(); - JOBS.put(jobId, request.getId()); manager.enqueue(request); } } From 15c5c90c6c3faaeb4c0e2745a39638e0ce6e970c Mon Sep 17 00:00:00 2001 From: emilypgoogle <110422458+emilypgoogle@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:08:10 -0500 Subject: [PATCH 6/9] Update transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java Co-authored-by: Rodrigo Lazo --- .../scheduling/jobscheduling/WorkManagerScheduler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java index fe6eaa2f636..f5cf4d5e8f1 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java @@ -80,10 +80,10 @@ public void schedule(TransportContext transportContext, int attemptNumber, boole } } - Data.Builder dataBuilder = new Data.Builder(); - dataBuilder.putInt(ATTEMPT_NUMBER, attemptNumber); - dataBuilder.putString(BACKEND_NAME, transportContext.getBackendName()); - dataBuilder.putInt(EVENT_PRIORITY, PriorityMapping.toInt(transportContext.getPriority())); + Data.Builder dataBuilder = new Data.Builder() + .putInt(ATTEMPT_NUMBER, attemptNumber) + .putString(BACKEND_NAME, transportContext.getBackendName()) + .putInt(EVENT_PRIORITY, PriorityMapping.toInt(transportContext.getPriority())); if (transportContext.getExtras() != null) { dataBuilder.putString(EXTRAS, encodeToString(transportContext.getExtras(), DEFAULT)); } From bd1ad495db283636d2533cc63d8f99365da80fdf Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Fri, 30 Aug 2024 16:14:29 -0500 Subject: [PATCH 7/9] Tag and exception --- .../scheduling/jobscheduling/WorkManagerScheduler.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java index f5cf4d5e8f1..1e9184446c3 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java @@ -55,6 +55,10 @@ public WorkManagerScheduler( this.config = config; } + private String getTag(int jobId) { + return "transport-" + jobId; + } + @Override public void schedule(TransportContext transportContext, int attemptNumber) { schedule(transportContext, attemptNumber, false); @@ -67,7 +71,7 @@ public void schedule(TransportContext transportContext, int attemptNumber, boole int jobId = WorkScheduler.getJobId(context, transportContext); if (!force) { try { - for (WorkInfo info : manager.getWorkInfosByTag("transport-" + jobId).get()) { + for (WorkInfo info : manager.getWorkInfosByTag(this.getTag(jobId)).get()) { if (!info.getState().isFinished()) { Logging.d( LOG_TAG, @@ -77,6 +81,8 @@ public void schedule(TransportContext transportContext, int attemptNumber, boole } } } catch (Exception e) { + // Various Future failure states that shouldn't be possible + throw new RuntimeException(e); } } @@ -107,7 +113,7 @@ public void schedule(TransportContext transportContext, int attemptNumber, boole new OneTimeWorkRequest.Builder(WorkManagerSchedulerWorker.class) .setInitialDelay(scheduleDelay, TimeUnit.MILLISECONDS) .setInputData(dataBuilder.build()) - .addTag("transport-" + jobId) + .addTag(this.getTag(jobId)) .build(); manager.enqueue(request); } From 0564a101620896fbba82fc6d6b6ea26e4dbc8644 Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Fri, 30 Aug 2024 16:22:23 -0500 Subject: [PATCH 8/9] Organize imports --- .../scheduling/jobscheduling/WorkManagerScheduler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java index 1e9184446c3..51fc49f22b1 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java @@ -19,19 +19,19 @@ import android.content.Context; import android.os.Build; + import androidx.annotation.RequiresApi; import androidx.work.Data; import androidx.work.OneTimeWorkRequest; import androidx.work.WorkInfo; import androidx.work.WorkManager; import androidx.work.WorkRequest; + import com.google.android.datatransport.runtime.TransportContext; import com.google.android.datatransport.runtime.logging.Logging; import com.google.android.datatransport.runtime.scheduling.persistence.EventStore; import com.google.android.datatransport.runtime.util.PriorityMapping; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; + import java.util.concurrent.TimeUnit; @RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) From 1a47878c892b7760647f27d455032f578e4dd9b1 Mon Sep 17 00:00:00 2001 From: Emily Ploszaj Date: Fri, 30 Aug 2024 16:59:56 -0500 Subject: [PATCH 9/9] Format --- .../scheduling/jobscheduling/WorkManagerScheduler.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java index 51fc49f22b1..c119457cba6 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/WorkManagerScheduler.java @@ -19,19 +19,16 @@ import android.content.Context; import android.os.Build; - import androidx.annotation.RequiresApi; import androidx.work.Data; import androidx.work.OneTimeWorkRequest; import androidx.work.WorkInfo; import androidx.work.WorkManager; import androidx.work.WorkRequest; - import com.google.android.datatransport.runtime.TransportContext; import com.google.android.datatransport.runtime.logging.Logging; import com.google.android.datatransport.runtime.scheduling.persistence.EventStore; import com.google.android.datatransport.runtime.util.PriorityMapping; - import java.util.concurrent.TimeUnit; @RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @@ -86,7 +83,8 @@ public void schedule(TransportContext transportContext, int attemptNumber, boole } } - Data.Builder dataBuilder = new Data.Builder() + Data.Builder dataBuilder = + new Data.Builder() .putInt(ATTEMPT_NUMBER, attemptNumber) .putString(BACKEND_NAME, transportContext.getBackendName()) .putInt(EVENT_PRIORITY, PriorityMapping.toInt(transportContext.getPriority()));