• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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.ondevicepersonalization.services.data.user;
18 
19 import static com.android.adservices.shared.proto.JobPolicy.BatteryType.BATTERY_TYPE_REQUIRE_NOT_LOW;
20 import static com.android.adservices.shared.proto.JobPolicy.NetworkType.NETWORK_TYPE_NONE;
21 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
22 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_DISABLED_FOR_USER_CONSENT_REVOKED;
23 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_ENABLED;
24 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID;
25 
26 import android.content.Context;
27 
28 import com.android.adservices.shared.proto.JobPolicy;
29 import com.android.adservices.shared.spe.framework.ExecutionResult;
30 import com.android.adservices.shared.spe.framework.ExecutionRuntimeParameters;
31 import com.android.adservices.shared.spe.framework.JobWorker;
32 import com.android.adservices.shared.spe.scheduling.BackoffPolicy;
33 import com.android.adservices.shared.spe.scheduling.JobSpec;
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
36 import com.android.ondevicepersonalization.services.Flags;
37 import com.android.ondevicepersonalization.services.FlagsFactory;
38 import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication;
39 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
40 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobScheduler;
41 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobServiceFactory;
42 
43 import com.google.common.util.concurrent.Futures;
44 import com.google.common.util.concurrent.ListenableFuture;
45 import com.google.common.util.concurrent.ListeningExecutorService;
46 
47 /** JobService to collect user data in the background thread. */
48 public final class UserDataCollectionJob implements JobWorker {
49     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
50     private static final String TAG = UserDataCollectionJob.class.getSimpleName();
51     // 4-hour interval.
52     private static final long PERIOD_SECONDS = 14400;
53     private UserDataCollector mUserDataCollector;
54     private RawUserData mUserData;
55 
56     private final Injector mInjector;
57 
UserDataCollectionJob()58     public UserDataCollectionJob() {
59         mInjector = new Injector();
60     }
61 
62     @VisibleForTesting
UserDataCollectionJob(Injector injector)63     public UserDataCollectionJob(Injector injector) {
64         mInjector = injector;
65     }
66 
67     static class Injector {
getExecutor()68         ListeningExecutorService getExecutor() {
69             return OnDevicePersonalizationExecutors.getBackgroundExecutor();
70         }
71 
getFlags()72         Flags getFlags() {
73             return FlagsFactory.getFlags();
74         }
75     }
76 
77     @Override
getExecutionFuture( Context context, ExecutionRuntimeParameters executionRuntimeParameters)78     public ListenableFuture<ExecutionResult> getExecutionFuture(
79             Context context, ExecutionRuntimeParameters executionRuntimeParameters) {
80         return Futures.submit(() -> {
81             startUserDataCollectionJob(context);
82             return ExecutionResult.SUCCESS;
83         }, mInjector.getExecutor());
84     }
85 
86     @Override
getJobEnablementStatus()87     public int getJobEnablementStatus() {
88         if (mInjector.getFlags().getGlobalKillSwitch()) {
89             sLogger.d(TAG + ": GlobalKillSwitch enabled, skip execution of UserDataCollectionJob.");
90             return JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
91         }
92         if (!mInjector.getFlags().getSpeOnUserDataCollectionJobEnabled()) {
93             sLogger.d(TAG + ": user data collection is disabled; skipping and cancelling job");
94             return JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
95         }
96         if (UserPrivacyStatus.getInstance().isProtectedAudienceAndMeasurementBothDisabled()) {
97             sLogger.d(TAG + ": consent revoked; "
98                     + "skipping, cancelling job, and deleting existing user data");
99             handlePrivacyControlsRevoked(OnDevicePersonalizationApplication.getAppContext());
100             return JOB_ENABLED_STATUS_DISABLED_FOR_USER_CONSENT_REVOKED;
101         }
102         return JOB_ENABLED_STATUS_ENABLED;
103     }
104 
105     @Override
getBackoffPolicy()106     public BackoffPolicy getBackoffPolicy() {
107         return new BackoffPolicy.Builder().setShouldRetryOnExecutionStop(true).build();
108     }
109 
110     /** Schedules a unique instance of {@link UserDataCollectionJob}. */
schedule(Context context)111     public static void schedule(Context context) {
112         // If SPE is not enabled, force to schedule the job with the old JobService.
113         if (!FlagsFactory.getFlags().getSpeOnUserDataCollectionJobEnabled()) {
114             sLogger.d("SPE is not enabled. Schedule the job with UserDataCollectionJobService.");
115 
116             int resultCode =
117                     UserDataCollectionJobService.schedule(context, /* forceSchedule */ false);
118             OdpJobServiceFactory.getInstance(context)
119                     .getJobSchedulingLogger()
120                     .recordOnSchedulingLegacy(USER_DATA_COLLECTION_ID, resultCode);
121 
122             return;
123         }
124 
125         OdpJobScheduler.getInstance(context).schedule(context, createDefaultJobSpec());
126     }
127 
128     @VisibleForTesting
createDefaultJobSpec()129     static JobSpec createDefaultJobSpec() {
130         JobPolicy jobPolicy =
131                 JobPolicy.newBuilder()
132                         .setJobId(USER_DATA_COLLECTION_ID)
133                         .setRequireDeviceIdle(true)
134                         .setBatteryType(BATTERY_TYPE_REQUIRE_NOT_LOW)
135                         .setRequireStorageNotLow(true)
136                         .setNetworkType(NETWORK_TYPE_NONE)
137                         .setPeriodicJobParams(
138                                 JobPolicy.PeriodicJobParams.newBuilder()
139                                         .setPeriodicIntervalMs(1000 * PERIOD_SECONDS))
140                         .setIsPersisted(true)
141                         .build();
142         return new JobSpec.Builder(jobPolicy).build();
143     }
144 
startUserDataCollectionJob(Context context)145     private void startUserDataCollectionJob(Context context) {
146         mUserDataCollector = UserDataCollector.getInstance(context);
147         mUserData = RawUserData.getInstance();
148         mUserDataCollector.updateUserData(mUserData);
149     }
150 
handlePrivacyControlsRevoked(Context context)151     private void handlePrivacyControlsRevoked(Context context) {
152         mUserDataCollector = UserDataCollector.getInstance(context);
153         mUserData = RawUserData.getInstance();
154         mUserDataCollector.clearUserData(mUserData);
155         mUserDataCollector.clearMetadata();
156     }
157 }
158