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.shared.spe.logging; 18 19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__FAILED_WITHOUT_RETRY; 20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__FAILED_WITH_RETRY; 21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__HALTED_FOR_UNKNOWN_REASON; 22 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__ONSTOP_CALLED_WITHOUT_RETRY; 23 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__ONSTOP_CALLED_WITH_RETRY; 24 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SUCCESSFUL; 25 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SPE_FAIL_TO_COMMIT_JOB_EXECUTION_START_TIME; 26 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SPE_FAIL_TO_COMMIT_JOB_EXECUTION_STOP_TIME; 27 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SPE_INVALID_EXECUTION_PERIOD; 28 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SPE_UNAVAILABLE_JOB_EXECUTION_START_TIMESTAMP; 29 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON; 30 import static com.android.adservices.shared.spe.JobServiceConstants.EXECUTION_LOGGING_UNKNOWN_MODULE_NAME; 31 import static com.android.adservices.shared.spe.JobServiceConstants.MAX_PERCENTAGE; 32 import static com.android.adservices.shared.spe.JobServiceConstants.MILLISECONDS_PER_MINUTE; 33 import static com.android.adservices.shared.spe.JobServiceConstants.SHARED_PREFS_BACKGROUND_JOBS; 34 import static com.android.adservices.shared.spe.JobServiceConstants.UNAVAILABLE_JOB_EXECUTION_PERIOD; 35 import static com.android.adservices.shared.spe.JobServiceConstants.UNAVAILABLE_JOB_EXECUTION_START_TIMESTAMP; 36 import static com.android.adservices.shared.spe.JobServiceConstants.UNAVAILABLE_JOB_EXECUTION_STOP_TIMESTAMP; 37 import static com.android.adservices.shared.spe.JobServiceConstants.UNAVAILABLE_JOB_LATENCY; 38 import static com.android.adservices.shared.spe.JobServiceConstants.UNAVAILABLE_STOP_REASON; 39 import static com.android.adservices.shared.spe.framework.ExecutionResult.FAILURE_WITHOUT_RETRY; 40 import static com.android.adservices.shared.spe.framework.ExecutionResult.FAILURE_WITH_RETRY; 41 import static com.android.adservices.shared.spe.framework.ExecutionResult.SUCCESS; 42 import static com.android.adservices.shared.util.LogUtil.VERBOSE; 43 44 import android.annotation.NonNull; 45 import android.annotation.TargetApi; 46 import android.app.job.JobParameters; 47 import android.app.job.JobService; 48 import android.content.Context; 49 import android.content.SharedPreferences; 50 import android.os.Build; 51 52 import com.android.adservices.shared.common.flags.ModuleSharedFlags; 53 import com.android.adservices.shared.errorlogging.AdServicesErrorLogger; 54 import com.android.adservices.shared.spe.JobServiceConstants; 55 import com.android.adservices.shared.spe.framework.AbstractJobService; 56 import com.android.adservices.shared.spe.framework.ExecutionResult; 57 import com.android.adservices.shared.util.Clock; 58 import com.android.adservices.shared.util.LogUtil; 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.modules.utils.build.SdkLevel; 61 62 import com.google.common.util.concurrent.MoreExecutors; 63 64 import java.util.Map; 65 import java.util.Random; 66 import java.util.concurrent.Executor; 67 import java.util.concurrent.locks.ReadWriteLock; 68 import java.util.concurrent.locks.ReentrantReadWriteLock; 69 70 /** Class for logging methods used by background jobs. */ 71 // TODO(b/325292968): make this class final after all Jobs migrated to using SPE. 72 public class JobServiceLogger { 73 private static final ReadWriteLock sReadWriteLock = new ReentrantReadWriteLock(); 74 private static final Random sRandom = new Random(); 75 76 private final Context mContext; 77 private final Clock mClock; 78 private final StatsdJobServiceLogger mStatsdLogger; 79 private final AdServicesErrorLogger mErrorLogger; 80 // JobService runs the execution on the main thread, so the logging part should be offloaded to 81 // a separated thread. However, these logging events should be in sequence, respecting to the 82 // start and the end of an execution. 83 private final Executor mLoggingExecutor; 84 private final Map<Integer, String> mJobInfoMap; 85 private final ModuleSharedFlags mFlags; 86 87 /** Create an instance of {@link JobServiceLogger}. */ JobServiceLogger( Context context, Clock clock, StatsdJobServiceLogger statsdLogger, AdServicesErrorLogger errorLogger, Executor executor, Map<Integer, String> jobIdToNameMap, ModuleSharedFlags flags)88 public JobServiceLogger( 89 Context context, 90 Clock clock, 91 StatsdJobServiceLogger statsdLogger, 92 AdServicesErrorLogger errorLogger, 93 Executor executor, 94 Map<Integer, String> jobIdToNameMap, 95 ModuleSharedFlags flags) { 96 mContext = context; 97 mClock = clock; 98 mStatsdLogger = statsdLogger; 99 mErrorLogger = errorLogger; 100 mLoggingExecutor = MoreExecutors.newSequentialExecutor(executor); 101 mJobInfoMap = jobIdToNameMap; 102 mFlags = flags; 103 } 104 105 /** 106 * {@link JobService} calls this method in {@link JobService#onStartJob(JobParameters)} to 107 * record that onStartJob was called. 108 * 109 * @param jobId the unique id of the job to log for. 110 */ recordOnStartJob(int jobId)111 public void recordOnStartJob(int jobId) { 112 if (!mFlags.getBackgroundJobsLoggingEnabled()) { 113 return; 114 } 115 116 long startJobTimestamp = mClock.currentTimeMillis(); 117 118 mLoggingExecutor.execute(() -> persistJobExecutionData(jobId, startJobTimestamp)); 119 } 120 121 /** 122 * Records that the {@link JobService#jobFinished(JobParameters, boolean)} is called or is about 123 * to be called. 124 * 125 * @param jobId the unique id of the job to log for. 126 * @param isSuccessful indicates if the execution is successful. 127 * @param shouldRetry indicates whether to retry the execution. 128 */ 129 // TODO(b/325292968): make this method private once all jobs migrated to using SPE. recordJobFinished(int jobId, boolean isSuccessful, boolean shouldRetry)130 public void recordJobFinished(int jobId, boolean isSuccessful, boolean shouldRetry) { 131 if (!mFlags.getBackgroundJobsLoggingEnabled()) { 132 return; 133 } 134 135 int resultCode = 136 isSuccessful 137 ? AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SUCCESSFUL 138 : (shouldRetry 139 ? AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__FAILED_WITH_RETRY 140 : AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__FAILED_WITHOUT_RETRY); 141 142 mLoggingExecutor.execute( 143 () -> 144 logExecutionStats( 145 jobId, 146 mClock.currentTimeMillis(), 147 resultCode, 148 UNAVAILABLE_STOP_REASON)); 149 } 150 151 /** 152 * Records that the {@link JobService#jobFinished(JobParameters, boolean)} is called or is about 153 * to be called. 154 * 155 * <p>This is used by {@link AbstractJobService}, a part of SPE (Scheduling Policy Engine) 156 * framework. 157 * 158 * @param jobId the unique id of the job to log for. 159 * @param executionResult the {@link ExecutionResult} for current execution. 160 */ recordJobFinished(int jobId, ExecutionResult executionResult)161 public void recordJobFinished(int jobId, ExecutionResult executionResult) { 162 if (!mFlags.getBackgroundJobsLoggingEnabled()) { 163 return; 164 } 165 166 boolean isSuccessful = false; 167 boolean shouldRetry = false; 168 169 if (executionResult.equals(SUCCESS)) { 170 isSuccessful = true; 171 } else if (executionResult.equals(FAILURE_WITH_RETRY)) { 172 shouldRetry = true; 173 } else if (!executionResult.equals(FAILURE_WITHOUT_RETRY)) { 174 // Throws if the execution result to log is not one of SUCCESS, FAILURE_WITH_RETRY, or 175 // FAILURE_WITHOUT_RETRY. 176 throw new IllegalStateException( 177 "Invalid ExecutionResult: " + executionResult + ", jobId: " + jobId); 178 } 179 180 recordJobFinished(jobId, isSuccessful, shouldRetry); 181 } 182 183 /** 184 * {@link JobService} calls this method in {@link JobService#onStopJob(JobParameters)}} to 185 * enable logging. 186 * 187 * @param params configured {@link JobParameters} 188 * @param jobId the unique id of the job to log for. 189 * @param shouldRetry whether to reschedule the job. 190 */ 191 @TargetApi(Build.VERSION_CODES.S) recordOnStopJob(@onNull JobParameters params, int jobId, boolean shouldRetry)192 public void recordOnStopJob(@NonNull JobParameters params, int jobId, boolean shouldRetry) { 193 if (!mFlags.getBackgroundJobsLoggingEnabled()) { 194 return; 195 } 196 197 long endJobTimestamp = mClock.currentTimeMillis(); 198 199 // StopReason is only supported for Android Version S+. 200 int stopReason = SdkLevel.isAtLeastS() ? params.getStopReason() : UNAVAILABLE_STOP_REASON; 201 202 int resultCode = 203 shouldRetry 204 ? AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__ONSTOP_CALLED_WITH_RETRY 205 : AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__ONSTOP_CALLED_WITHOUT_RETRY; 206 207 mLoggingExecutor.execute( 208 () -> logExecutionStats(jobId, endJobTimestamp, resultCode, stopReason)); 209 } 210 211 /** 212 * Log when the execution is skipped due to customized reasons. 213 * 214 * @param jobId the unique id of the job to log for 215 * @param skipReason the result to skip the execution 216 */ recordJobSkipped(int jobId, int skipReason)217 public void recordJobSkipped(int jobId, int skipReason) { 218 if (!mFlags.getBackgroundJobsLoggingEnabled()) { 219 return; 220 } 221 222 mLoggingExecutor.execute( 223 () -> 224 logExecutionStats( 225 jobId, 226 mClock.currentTimeMillis(), 227 skipReason, 228 UNAVAILABLE_STOP_REASON)); 229 } 230 231 /** 232 * Log for various lifecycles of an execution. 233 * 234 * <p>a completed lifecycle includes job finished in {@link 235 * JobService#jobFinished(JobParameters, boolean)} or {@link 236 * JobService#onStopJob(JobParameters)}. 237 * 238 * @param jobId the job id 239 * @param jobStopExecutionTimestamp the timestamp of the end of an execution. Note it can happen 240 * in either {@link JobService#jobFinished(JobParameters, boolean)} or {@link 241 * JobService#onStopJob(JobParameters)}. 242 * @param executionResultCode the result code for current execution 243 * @param possibleStopReason if {@link JobService#onStopJob(JobParameters)} is invoked. Set 244 * {@link JobServiceConstants#UNAVAILABLE_STOP_REASON} if {@link 245 * JobService#onStopJob(JobParameters)} is not invoked. 246 */ 247 @VisibleForTesting logExecutionStats( int jobId, long jobStopExecutionTimestamp, int executionResultCode, int possibleStopReason)248 public void logExecutionStats( 249 int jobId, 250 long jobStopExecutionTimestamp, 251 int executionResultCode, 252 int possibleStopReason) { 253 String jobStartTimestampKey = getJobStartTimestampKey(jobId); 254 String executionPeriodKey = getExecutionPeriodKey(jobId); 255 String jobStopTimestampKey = getJobStopTimestampKey(jobId); 256 257 SharedPreferences sharedPreferences = 258 mContext.getSharedPreferences(SHARED_PREFS_BACKGROUND_JOBS, Context.MODE_PRIVATE); 259 SharedPreferences.Editor editor = sharedPreferences.edit(); 260 261 long jobStartExecutionTimestamp; 262 long jobExecutionPeriodMs; 263 264 sReadWriteLock.readLock().lock(); 265 try { 266 267 jobStartExecutionTimestamp = 268 sharedPreferences.getLong( 269 jobStartTimestampKey, UNAVAILABLE_JOB_EXECUTION_START_TIMESTAMP); 270 271 jobExecutionPeriodMs = 272 sharedPreferences.getLong(executionPeriodKey, UNAVAILABLE_JOB_EXECUTION_PERIOD); 273 } finally { 274 sReadWriteLock.readLock().unlock(); 275 } 276 277 // Stop telemetry the metrics and log error in logcat if the stat is not valid. 278 if (jobStartExecutionTimestamp == UNAVAILABLE_JOB_EXECUTION_START_TIMESTAMP 279 || jobStartExecutionTimestamp > jobStopExecutionTimestamp) { 280 LogUtil.e( 281 "Execution Stat is INVALID for job %s, jobStartTimestamp: %d, jobStopTimestamp:" 282 + " %d.", 283 mJobInfoMap.get(jobId), jobStartExecutionTimestamp, jobStopExecutionTimestamp); 284 mErrorLogger.logError( 285 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SPE_UNAVAILABLE_JOB_EXECUTION_START_TIMESTAMP, 286 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON); 287 return; 288 } 289 290 // Compute the execution latency. 291 long executionLatencyMs = jobStopExecutionTimestamp - jobStartExecutionTimestamp; 292 293 // Update jobStopExecutionTimestamp in storage. 294 editor.putLong(jobStopTimestampKey, jobStopExecutionTimestamp); 295 296 sReadWriteLock.writeLock().lock(); 297 try { 298 if (!editor.commit()) { 299 // The commitment failure should be rare. It may result in 1 problematic data but 300 // the impact could be ignored compared to a job's lifecycle. 301 LogUtil.e( 302 "Failed to update job Ending Execution Logging Data for Job %s, Job ID =" 303 + " %d.", 304 mJobInfoMap.get(jobId), jobId); 305 mErrorLogger.logError( 306 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SPE_FAIL_TO_COMMIT_JOB_EXECUTION_STOP_TIME, 307 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON); 308 } 309 } finally { 310 sReadWriteLock.writeLock().unlock(); 311 } 312 313 // Actually upload the metrics to statsD. 314 logJobStatsHelper( 315 jobId, 316 executionLatencyMs, 317 jobExecutionPeriodMs, 318 executionResultCode, 319 possibleStopReason); 320 } 321 322 /** 323 * Do background job telemetry. 324 * 325 * @param jobId the job ID 326 * @param executionLatencyMs the latency of an execution. Defined as the difference of timestamp 327 * between end and start of an execution. 328 * @param executionPeriodMs the execution period. Defined as the difference of timestamp between 329 * current and previous start of an execution. This is only valid for periodical jobs to 330 * monitor the difference between actual and configured execution period. 331 * @param resultCode the result code of an execution 332 * @param stopReason {@link JobParameters#getStopReason()} if {@link 333 * JobService#onStopJob(JobParameters)} is invoked. Otherwise, set it to {@link 334 * JobServiceConstants#UNAVAILABLE_STOP_REASON}. 335 */ 336 @VisibleForTesting logJobStatsHelper( int jobId, long executionLatencyMs, long executionPeriodMs, int resultCode, int stopReason)337 public void logJobStatsHelper( 338 int jobId, 339 long executionLatencyMs, 340 long executionPeriodMs, 341 int resultCode, 342 int stopReason) { 343 if (!shouldLog()) { 344 if (VERBOSE) { 345 LogUtil.v( 346 "This background job logging isn't selected for sampling logging, skip..."); 347 } 348 return; 349 } 350 351 // Since the execution period will be logged with unit of minute, it will be converted to 0 352 // if less than MILLISECONDS_PER_MINUTE. The negative period has two scenarios: 1) the first 353 // execution (as -1) or 2) invalid, which will be logged by CEL. As all negative values will 354 // be filtered out in the server's metric, keep the original value of them, to avoid a 0 355 // value due to small negative values. 356 long executionPeriodMinute = 357 executionPeriodMs >= 0 358 ? executionPeriodMs / MILLISECONDS_PER_MINUTE 359 : executionPeriodMs; 360 361 ExecutionReportedStats stats = 362 ExecutionReportedStats.builder() 363 .setJobId(jobId) 364 .setExecutionLatencyMs(convertLongToInteger(executionLatencyMs)) 365 .setExecutionPeriodMinute(convertLongToInteger(executionPeriodMinute)) 366 .setExecutionResultCode(resultCode) 367 .setStopReason(stopReason) 368 // TODO(b/324323522): Populate correct module name. 369 .setModuleName(EXECUTION_LOGGING_UNKNOWN_MODULE_NAME) 370 .build(); 371 mStatsdLogger.logExecutionReportedStats(stats); 372 373 if (VERBOSE) { 374 LogUtil.v( 375 "[Background job execution logging] jobId: %d, executionLatencyInMs: %d," 376 + " executionPeriodInMs: %d, resultCode: %d, stopReason: %d, moduleName:" 377 + " %d", 378 jobId, 379 executionLatencyMs, 380 executionPeriodMs, 381 resultCode, 382 stopReason, 383 EXECUTION_LOGGING_UNKNOWN_MODULE_NAME); 384 } 385 } 386 387 /** 388 * Compute execution data such as latency and period then store the data in persistent so that 389 * we can compute the job stats later. Store start job timestamp and execution period into the 390 * storage. 391 * 392 * @param jobId the job id 393 * @param startJobTimestamp the timestamp when {@link JobService#onStartJob(JobParameters)} is 394 * invoked. 395 */ 396 @VisibleForTesting persistJobExecutionData(int jobId, long startJobTimestamp)397 public void persistJobExecutionData(int jobId, long startJobTimestamp) { 398 SharedPreferences sharedPreferences = 399 mContext.getSharedPreferences(SHARED_PREFS_BACKGROUND_JOBS, Context.MODE_PRIVATE); 400 401 String jobStartTimestampKey = getJobStartTimestampKey(jobId); 402 String executionPeriodKey = getExecutionPeriodKey(jobId); 403 String jobStopTimestampKey = getJobStopTimestampKey(jobId); 404 405 // When onStartJob() is invoked, the data stored in the shared preference is for previous 406 // execution. 407 // 408 // JobService is scheduled as JobStatus in JobScheduler infra. Before a JobStatus instance 409 // is pushed to pendingJobQueue, it checks a few criteria like whether a same JobStatus is 410 // ready to execute, not pending, not running, etc. To determine if two JobStatus instances 411 // are same, it checks jobId, callingUid (the package that schedules the job). Therefore, 412 // there won't have two pending/running job instances with a same jobId. For more details, 413 // please check source code of JobScheduler. 414 long previousJobStartTimestamp; 415 long previousJobStopTimestamp; 416 long previousExecutionPeriod; 417 418 sReadWriteLock.readLock().lock(); 419 try { 420 previousJobStartTimestamp = 421 sharedPreferences.getLong( 422 jobStartTimestampKey, UNAVAILABLE_JOB_EXECUTION_START_TIMESTAMP); 423 previousJobStopTimestamp = 424 sharedPreferences.getLong( 425 jobStopTimestampKey, UNAVAILABLE_JOB_EXECUTION_STOP_TIMESTAMP); 426 previousExecutionPeriod = 427 sharedPreferences.getLong(executionPeriodKey, UNAVAILABLE_JOB_EXECUTION_PERIOD); 428 } finally { 429 sReadWriteLock.readLock().unlock(); 430 } 431 432 SharedPreferences.Editor editor = sharedPreferences.edit(); 433 434 // The first execution, pass execution period with UNAVAILABLE_JOB_EXECUTION_PERIOD. 435 if (previousJobStartTimestamp == UNAVAILABLE_JOB_EXECUTION_START_TIMESTAMP) { 436 editor.putLong(executionPeriodKey, UNAVAILABLE_JOB_EXECUTION_PERIOD); 437 } else { 438 // If previousJobStartTimestamp is later than previousJobStopTimestamp, it indicates the 439 // last execution didn't finish with calling jobFinished() or onStopJob(). In this case, 440 // we log as an unknown issue, which may come from system/device. 441 if (previousJobStartTimestamp > previousJobStopTimestamp) { 442 logJobStatsHelper( 443 jobId, 444 UNAVAILABLE_JOB_LATENCY, 445 previousExecutionPeriod, 446 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__HALTED_FOR_UNKNOWN_REASON, 447 UNAVAILABLE_STOP_REASON); 448 } 449 450 // Compute execution period if there has been multiple executions. 451 // Define the execution period = difference of the timestamp of two consecutive 452 // invocations of onStartJob(). 453 long executionPeriodInMs = startJobTimestamp - previousJobStartTimestamp; 454 if (executionPeriodInMs < 0) { 455 LogUtil.e( 456 "Invalid execution period = %d! Start time for current execution should be" 457 + " later than previous execution!", 458 executionPeriodInMs); 459 mErrorLogger.logError( 460 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SPE_INVALID_EXECUTION_PERIOD, 461 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON); 462 } 463 464 // Store the execution period into shared preference. 465 editor.putLong(executionPeriodKey, executionPeriodInMs); 466 } 467 // Store current JobStartTimestamp into shared preference. 468 editor.putLong(jobStartTimestampKey, startJobTimestamp); 469 470 sReadWriteLock.writeLock().lock(); 471 try { 472 if (!editor.commit()) { 473 // The commitment failure should be rare. It may result in 1 problematic data but 474 // the impact could be ignored compared to a job's lifecycle. 475 LogUtil.e( 476 "Failed to update onStartJob() Logging Data for Job %s, Job ID = %d", 477 mJobInfoMap.get(jobId), jobId); 478 mErrorLogger.logError( 479 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SPE_FAIL_TO_COMMIT_JOB_EXECUTION_START_TIME, 480 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON); 481 } 482 } finally { 483 sReadWriteLock.writeLock().unlock(); 484 } 485 } 486 487 @VisibleForTesting getJobStartTimestampKey(int jobId)488 static String getJobStartTimestampKey(int jobId) { 489 return jobId + JobServiceConstants.SHARED_PREFS_START_TIMESTAMP_SUFFIX; 490 } 491 492 @VisibleForTesting getJobStopTimestampKey(int jobId)493 static String getJobStopTimestampKey(int jobId) { 494 return jobId + JobServiceConstants.SHARED_PREFS_STOP_TIMESTAMP_SUFFIX; 495 } 496 497 @VisibleForTesting getExecutionPeriodKey(int jobId)498 static String getExecutionPeriodKey(int jobId) { 499 return jobId + JobServiceConstants.SHARED_PREFS_EXEC_PERIOD_SUFFIX; 500 } 501 502 // Convert a long value to an integer. 503 // 504 // Used to convert a time period in long-format but needs to be logged with integer-format. 505 // Generally, a time period should always be a positive integer with a proper design of its 506 // unit. 507 // 508 // Defensively use this method to avoid any Exception. 509 @VisibleForTesting convertLongToInteger(long longVal)510 static int convertLongToInteger(long longVal) { 511 int intValue; 512 513 // The given time period should always be in the range of positive integer. Defensively 514 // handle overflow values to avoid potential Exceptions. 515 if (longVal <= Integer.MIN_VALUE) { 516 intValue = Integer.MIN_VALUE; 517 } else if (longVal >= Integer.MAX_VALUE) { 518 intValue = Integer.MAX_VALUE; 519 } else { 520 intValue = (int) longVal; 521 } 522 523 return intValue; 524 } 525 526 // Make a random draw to determine if a logging event should be uploaded t0 the logging server. 527 @VisibleForTesting shouldLog()528 boolean shouldLog() { 529 int loggingRatio = mFlags.getBackgroundJobSamplingLoggingRate(); 530 531 return sRandom.nextInt(MAX_PERCENTAGE) < loggingRatio; 532 } 533 } 534