1 /* 2 * Copyright (C) 2023 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.federatedcompute.services.encryption; 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 21 import android.app.job.JobInfo; 22 import android.app.job.JobParameters; 23 import android.app.job.JobScheduler; 24 import android.app.job.JobService; 25 import android.content.ComponentName; 26 import android.content.Context; 27 28 import com.android.federatedcompute.internal.util.LogUtil; 29 import com.android.federatedcompute.services.common.FederatedComputeExecutors; 30 import com.android.federatedcompute.services.common.FederatedComputeJobInfo; 31 import com.android.federatedcompute.services.common.FederatedComputeJobUtil; 32 import com.android.federatedcompute.services.common.Flags; 33 import com.android.federatedcompute.services.common.FlagsFactory; 34 import com.android.federatedcompute.services.data.FederatedComputeEncryptionKey; 35 import com.android.federatedcompute.services.statsd.joblogging.FederatedComputeJobServiceLogger; 36 import com.android.internal.annotations.VisibleForTesting; 37 38 import com.google.common.util.concurrent.FutureCallback; 39 import com.google.common.util.concurrent.Futures; 40 import com.google.common.util.concurrent.ListeningExecutorService; 41 42 import java.util.List; 43 import java.util.concurrent.ExecutionException; 44 import java.util.concurrent.TimeoutException; 45 46 public class BackgroundKeyFetchJobService extends JobService { 47 private static final String TAG = BackgroundKeyFetchJobService.class.getSimpleName(); 48 49 private static final int ENCRYPTION_KEY_FETCH_JOB_ID = 50 FederatedComputeJobInfo.ENCRYPTION_KEY_FETCH_JOB_ID; 51 52 static class Injector { getExecutor()53 ListeningExecutorService getExecutor() { 54 return FederatedComputeExecutors.getBackgroundExecutor(); 55 } 56 getLightWeightExecutor()57 ListeningExecutorService getLightWeightExecutor() { 58 return FederatedComputeExecutors.getLightweightExecutor(); 59 } 60 getEncryptionKeyManager(Context context)61 FederatedComputeEncryptionKeyManager getEncryptionKeyManager(Context context) { 62 return FederatedComputeEncryptionKeyManager.getInstance(context); 63 } 64 } 65 66 private final Injector mInjector; 67 BackgroundKeyFetchJobService()68 public BackgroundKeyFetchJobService() { 69 mInjector = new Injector(); 70 } 71 72 @VisibleForTesting BackgroundKeyFetchJobService(Injector injector)73 BackgroundKeyFetchJobService(Injector injector) { 74 mInjector = injector; 75 } 76 77 /** Runs the background key fetch and persist keys job. The method is for testing only. */ 78 @VisibleForTesting run(JobParameters params)79 public void run(JobParameters params) { 80 var unused = Futures.submit(() -> onStartJob(params), mInjector.getExecutor()); 81 } 82 83 @Override onStartJob(JobParameters params)84 public boolean onStartJob(JobParameters params) { 85 LogUtil.d(TAG, "BackgroundKeyFetchJobService.onStartJob %d", params.getJobId()); 86 FederatedComputeJobServiceLogger.getInstance(this) 87 .recordOnStartJob(ENCRYPTION_KEY_FETCH_JOB_ID); 88 if (FlagsFactory.getFlags().getGlobalKillSwitch()) { 89 LogUtil.d(TAG, "GlobalKillSwitch enabled, finishing job."); 90 return FederatedComputeJobUtil.cancelAndFinishJob(this, params, 91 ENCRYPTION_KEY_FETCH_JOB_ID, 92 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON); 93 } 94 mInjector 95 .getEncryptionKeyManager(this) 96 .fetchAndPersistActiveKeys(FederatedComputeEncryptionKey.KEY_TYPE_ENCRYPTION, 97 /* isScheduledJob= */ true) 98 .addCallback( 99 new FutureCallback<List<FederatedComputeEncryptionKey>>() { 100 @Override 101 public void onSuccess( 102 List<FederatedComputeEncryptionKey> 103 federatedComputeEncryptionKeys) { 104 LogUtil.d( 105 TAG, 106 "BackgroundKeyFetchJobService %d is done, fetched %d keys", 107 params.getJobId(), 108 federatedComputeEncryptionKeys.size()); 109 boolean wantsReschedule = false; 110 FederatedComputeJobServiceLogger.getInstance( 111 BackgroundKeyFetchJobService.this) 112 .recordJobFinished( 113 ENCRYPTION_KEY_FETCH_JOB_ID, 114 /* isSuccessful= */ true, 115 wantsReschedule); 116 jobFinished(params, wantsReschedule); 117 } 118 119 @Override 120 public void onFailure(Throwable throwable) { 121 LogUtil.e( 122 TAG, 123 "Failed to run job %d to fetch key and delete expired keys", 124 params.getJobId()); 125 if (throwable instanceof ExecutionException) { 126 LogUtil.e( 127 TAG, 128 "Background key fetch failed due to internal error"); 129 } else if (throwable instanceof TimeoutException) { 130 LogUtil.e( 131 TAG, 132 "Background key fetch failed due to timeout error"); 133 } else if (throwable instanceof InterruptedException) { 134 LogUtil.e( 135 TAG, 136 "Background key fetch failed due to interruption " 137 + "error"); 138 } else if (throwable instanceof IllegalArgumentException) { 139 LogUtil.e( 140 TAG, 141 "Background key fetch failed due to illegal argument " 142 + "error"); 143 } else { 144 LogUtil.e( 145 TAG, 146 "Background key fetch failed due to unexpected error"); 147 } 148 boolean wantsReschedule = false; 149 FederatedComputeJobServiceLogger.getInstance( 150 BackgroundKeyFetchJobService.this) 151 .recordJobFinished( 152 ENCRYPTION_KEY_FETCH_JOB_ID, 153 /* isSuccessful= */ false, 154 wantsReschedule); 155 jobFinished(params, wantsReschedule); 156 } 157 }, 158 mInjector.getLightWeightExecutor()); 159 return true; 160 } 161 162 @Override onStopJob(JobParameters params)163 public boolean onStopJob(JobParameters params) { 164 LogUtil.d(TAG, "BackgroundKeyFetchJobService.onStopJob %d", params.getJobId()); 165 boolean wantsReschedule = false; 166 FederatedComputeJobServiceLogger.getInstance(this) 167 .recordOnStopJob( 168 params, 169 ENCRYPTION_KEY_FETCH_JOB_ID, 170 wantsReschedule); 171 return wantsReschedule; 172 } 173 174 /** Schedule the periodic background key fetch and delete job if it is not scheduled. */ scheduleJobIfNeeded(Context context, Flags flags)175 public static boolean scheduleJobIfNeeded(Context context, Flags flags) { 176 if (!flags.getEnableBackgroundEncryptionKeyFetch()) { 177 LogUtil.d( 178 TAG, 179 "Schedule encryption key job fetch is not enable in flags."); 180 return false; 181 } 182 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 183 if (jobScheduler == null) { 184 LogUtil.e(TAG, "Failed to get job scheduler from system service."); 185 return false; 186 } 187 188 final JobInfo scheduledJob = jobScheduler.getPendingJob(ENCRYPTION_KEY_FETCH_JOB_ID); 189 final JobInfo jobInfo = 190 new JobInfo.Builder( 191 ENCRYPTION_KEY_FETCH_JOB_ID, 192 new ComponentName(context, BackgroundKeyFetchJobService.class)) 193 .setPeriodic( 194 flags.getEncryptionKeyFetchPeriodSeconds() 195 * 1000) // convert to milliseconds 196 .setRequiresBatteryNotLow(true) 197 .setRequiresDeviceIdle(true) 198 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 199 .setPersisted(true) 200 .build(); 201 202 if (!jobInfo.equals(scheduledJob)) { 203 jobScheduler.schedule(jobInfo); 204 LogUtil.d( 205 TAG, 206 "Scheduled job BackgroundKeyFetchJobService id %d", 207 ENCRYPTION_KEY_FETCH_JOB_ID); 208 return true; 209 } else { 210 LogUtil.d( 211 TAG, 212 "Already scheduled job BackgroundKeyFetchJobService id %d", 213 ENCRYPTION_KEY_FETCH_JOB_ID); 214 return false; 215 } 216 } 217 } 218