1 /* 2 * Copyright (C) 2022 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.download.mdd; 18 19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON; 20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED; 21 import static com.android.ondevicepersonalization.services.download.mdd.MddTaskScheduler.getMddTaskTag; 22 23 import static com.google.android.libraries.mobiledatadownload.TaskScheduler.WIFI_CHARGING_PERIODIC_TASK; 24 25 import android.app.job.JobParameters; 26 import android.app.job.JobScheduler; 27 import android.app.job.JobService; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 31 import com.android.ondevicepersonalization.services.Flags; 32 import com.android.ondevicepersonalization.services.FlagsFactory; 33 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 34 import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus; 35 import com.android.ondevicepersonalization.services.download.OnDevicePersonalizationDownloadProcessingJob; 36 import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger; 37 38 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures; 39 import com.google.common.util.concurrent.FutureCallback; 40 import com.google.common.util.concurrent.Futures; 41 import com.google.common.util.concurrent.ListenableFuture; 42 import com.google.common.util.concurrent.ListeningExecutorService; 43 44 /** 45 * MDD JobService. This will download MDD files in background tasks. 46 */ 47 public class MddJobService extends JobService { 48 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 49 private static final String TAG = "MddJobService"; 50 51 private final Injector mInjector; 52 MddJobService()53 public MddJobService() { 54 mInjector = new Injector(); 55 } 56 57 @VisibleForTesting MddJobService(Injector injector)58 public MddJobService(Injector injector) { 59 mInjector = injector; 60 } 61 62 static class Injector { getBackgroundExecutor()63 ListeningExecutorService getBackgroundExecutor() { 64 return OnDevicePersonalizationExecutors.getBackgroundExecutor(); 65 } 66 getFlags()67 Flags getFlags() { 68 return FlagsFactory.getFlags(); 69 } 70 } 71 72 @Override onStartJob(JobParameters params)73 public boolean onStartJob(JobParameters params) { 74 sLogger.d(TAG + ": onStartJob()"); 75 OdpJobServiceLogger.getInstance(this).recordOnStartJob(params.getJobId()); 76 77 if (mInjector.getFlags().getGlobalKillSwitch()) { 78 sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job."); 79 return cancelAndFinishJob(params, 80 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON); 81 } 82 83 // Reschedule jobs with SPE if it's enabled. 84 // Note: scheduled jobs by this MddJobService will be cancelled for the same job ID 85 if (mInjector.getFlags().getSpeOnMddJobEnabled()) { 86 sLogger.d("SPE is enabled. Reschedule MddJobService with MddJob."); 87 MddTaskScheduler.schedule(/* context */ this, params.getExtras()); 88 return false; 89 } 90 91 // Run privacy status checks in the background 92 runPrivacyStatusChecksInBackgroundAndExecute(params); 93 return true; 94 } 95 runPrivacyStatusChecksInBackgroundAndExecute(final JobParameters params)96 private void runPrivacyStatusChecksInBackgroundAndExecute(final JobParameters params) { 97 OnDevicePersonalizationExecutors.getHighPriorityBackgroundExecutor().execute(() -> { 98 if (UserPrivacyStatus.getInstance().isProtectedAudienceAndMeasurementBothDisabled()) { 99 // User control is revoked; handle this case 100 sLogger.d(TAG + ": User control is not given for all ODP services."); 101 OdpJobServiceLogger.getInstance(MddJobService.this) 102 .recordJobSkipped(params.getJobId(), 103 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED); 104 jobFinished(params, false); 105 } else { 106 // User control is given; handle the MDD task 107 ListenableFuture<Void> handleTaskFuture = 108 PropagatedFutures.submitAsync( 109 () -> MobileDataDownloadFactory.getMdd(this) 110 .handleTask(getMddTaskTag(params.getExtras())), 111 mInjector.getBackgroundExecutor()); 112 113 Futures.addCallback( 114 handleTaskFuture, 115 new FutureCallback<Void>() { 116 @Override 117 public void onSuccess(Void result) { 118 handleSuccess(params); 119 } 120 121 @Override 122 public void onFailure(Throwable t) { 123 handleFailure(params, t); 124 } 125 }, 126 mInjector.getBackgroundExecutor()); 127 } 128 }); 129 } 130 handleSuccess(JobParameters params)131 private void handleSuccess(JobParameters params) { 132 sLogger.d(TAG + ": MddJobService.MddHandleTask succeeded!"); 133 if (WIFI_CHARGING_PERIODIC_TASK.equals(getMddTaskTag(params.getExtras()))) { 134 OnDevicePersonalizationDownloadProcessingJob.schedule(/* context */ this); 135 } 136 recordJobFinished(params.getJobId(), true); 137 jobFinished(params, false); 138 } 139 handleFailure(JobParameters params, Throwable throwable)140 private void handleFailure(JobParameters params, Throwable throwable) { 141 sLogger.e(TAG + ": Failed to handle JobService: " + params.getJobId(), throwable); 142 recordJobFinished(params.getJobId(), false); 143 jobFinished(params, false); 144 } 145 recordJobFinished(int jobId, boolean isSuccessful)146 private void recordJobFinished(int jobId, boolean isSuccessful) { 147 boolean wantsReschedule = false; 148 OdpJobServiceLogger.getInstance(this) 149 .recordJobFinished(jobId, isSuccessful, wantsReschedule); 150 } 151 152 @Override onStopJob(JobParameters params)153 public boolean onStopJob(JobParameters params) { 154 // Attempt to process any data downloaded before the worker was stopped. 155 if (WIFI_CHARGING_PERIODIC_TASK.equals(getMddTaskTag(params.getExtras()))) { 156 OnDevicePersonalizationDownloadProcessingJob.schedule(/* context */ this); 157 } 158 // Reschedule the job since it ended before finishing 159 boolean wantsReschedule = true; 160 OdpJobServiceLogger.getInstance(this) 161 .recordOnStopJob(params, params.getJobId(), wantsReschedule); 162 return wantsReschedule; 163 } 164 cancelAndFinishJob(final JobParameters params, int skipReason)165 private boolean cancelAndFinishJob(final JobParameters params, int skipReason) { 166 JobScheduler jobScheduler = this.getSystemService(JobScheduler.class); 167 int jobId = params.getJobId(); 168 if (jobScheduler != null) { 169 jobScheduler.cancel(jobId); 170 } 171 OdpJobServiceLogger.getInstance(this).recordJobSkipped(jobId, skipReason); 172 jobFinished(params, /* wantsReschedule = */ false); 173 return true; 174 } 175 } 176