1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.adservices.service.topics;
18 
19 import static com.android.adservices.service.Flags.TOPICS_EPOCH_JOB_FLEX_MS;
20 import static com.android.adservices.service.Flags.TOPICS_EPOCH_JOB_PERIOD_MS;
21 import static com.android.adservices.shared.proto.JobPolicy.BatteryType.BATTERY_TYPE_REQUIRE_CHARGING;
22 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
23 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_ENABLED;
24 import static com.android.adservices.shared.spe.framework.ExecutionResult.SUCCESS;
25 import static com.android.adservices.spe.AdServicesJobInfo.TOPICS_EPOCH_JOB;
26 
27 import android.content.Context;
28 import android.os.Build;
29 
30 import androidx.annotation.RequiresApi;
31 
32 import com.android.adservices.LoggerFactory;
33 import com.android.adservices.concurrency.AdServicesExecutors;
34 import com.android.adservices.service.FlagsFactory;
35 import com.android.adservices.shared.proto.JobPolicy;
36 import com.android.adservices.shared.spe.framework.ExecutionResult;
37 import com.android.adservices.shared.spe.framework.ExecutionRuntimeParameters;
38 import com.android.adservices.shared.spe.framework.JobWorker;
39 import com.android.adservices.shared.spe.scheduling.JobSpec;
40 import com.android.adservices.spe.AdServicesJobScheduler;
41 import com.android.adservices.spe.AdServicesJobServiceFactory;
42 import com.android.internal.annotations.VisibleForTesting;
43 
44 import com.google.common.util.concurrent.Futures;
45 import com.google.common.util.concurrent.ListenableFuture;
46 
47 /** Epoch computation job. This will be run approximately once per epoch to compute Topics. */
48 @RequiresApi(Build.VERSION_CODES.S)
49 public final class EpochJob implements JobWorker {
50     @Override
getExecutionFuture( Context context, ExecutionRuntimeParameters executionRuntimeParameters)51     public ListenableFuture<ExecutionResult> getExecutionFuture(
52             Context context, ExecutionRuntimeParameters executionRuntimeParameters) {
53         return Futures.submit(
54                 () -> {
55                     TopicsWorker.getInstance().computeEpoch();
56                     return SUCCESS;
57                 },
58                 AdServicesExecutors.getBackgroundExecutor());
59     }
60 
61     @Override
getJobEnablementStatus()62     public int getJobEnablementStatus() {
63         if (FlagsFactory.getFlags().getTopicsKillSwitch()) {
64             LoggerFactory.getTopicsLogger()
65                     .e("Topics API is disabled, skipping and cancelling EpochJobService");
66             return JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
67         }
68 
69         return JOB_ENABLED_STATUS_ENABLED;
70     }
71 
72     /** Schedules the {@link EpochJob}. */
schedule()73     public static void schedule() {
74         // If SPE is not enabled, force to schedule the job with the old JobService.
75         if (!FlagsFactory.getFlags().getSpeOnEpochJobEnabled()) {
76             LoggerFactory.getTopicsLogger()
77                     .d("SPE is not enabled. Schedule the job with EpochJobService.");
78             int resultCode = EpochJobService.scheduleIfNeeded(/* forceSchedule= */ false);
79 
80             AdServicesJobServiceFactory.getInstance()
81                     .getJobSchedulingLogger()
82                     .recordOnSchedulingLegacy(TOPICS_EPOCH_JOB.getJobId(), resultCode);
83             return;
84         }
85 
86         AdServicesJobScheduler.getInstance().schedule(createDefaultJobSpec());
87     }
88 
89     @VisibleForTesting
createDefaultJobSpec()90     static JobSpec createDefaultJobSpec() {
91         JobPolicy jobPolicy =
92                 JobPolicy.newBuilder()
93                         .setJobId(TOPICS_EPOCH_JOB.getJobId())
94                         .setBatteryType(BATTERY_TYPE_REQUIRE_CHARGING)
95                         .setIsPersisted(true)
96                         .setPeriodicJobParams(
97                                 JobPolicy.PeriodicJobParams.newBuilder()
98                                         .setPeriodicIntervalMs(TOPICS_EPOCH_JOB_PERIOD_MS)
99                                         .setFlexInternalMs(TOPICS_EPOCH_JOB_FLEX_MS)
100                                         .build())
101                         .build();
102 
103         return new JobSpec.Builder(jobPolicy).build();
104     }
105 }
106