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