Add client error logging and background job logging infra to ODP, flags
disabled by default
Bug:323574204
Test: atest OnDevicePersonalizationDownloadProcessingJobServiceTests
OnDevicePersonalizationMaintenanceJobServiceTest
OnDevicePersonalizationDataProcessingAsyncCallableTests
UserDataCollectionJobServiceTest MddJobServiceTest -c, manually executed job
1000-1006
Change-Id: I5ef653cb3c5ae3f0685623d70f44e7a339774ed5
diff --git a/Android.bp b/Android.bp
index 1a0fdbe..378b47e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -120,6 +120,7 @@
"owasp-java-encoder",
"tensorflowlite_java",
"tensorflow_core_proto_java_lite",
+ "adservices-shared-spe",
],
sdk_version: "module_current",
min_sdk_version: "33",
diff --git a/src/com/android/ondevicepersonalization/services/Flags.java b/src/com/android/ondevicepersonalization/services/Flags.java
index aff0a59..2f81145 100644
--- a/src/com/android/ondevicepersonalization/services/Flags.java
+++ b/src/com/android/ondevicepersonalization/services/Flags.java
@@ -16,13 +16,15 @@
package com.android.ondevicepersonalization.services;
+import com.android.adservices.shared.common.flags.ModuleSharedFlags;
+
/**
* OnDevicePersonalization Feature Flags interface. This Flags interface hold the default values
* of flags. The default values in this class must match with the default values in PH since we
* will migrate to Flag Codegen in the future. With that migration, the Flags.java file will be
* generated from the GCL.
*/
-public interface Flags {
+public interface Flags extends ModuleSharedFlags {
/**
* Global OnDevicePersonalization Kill Switch. This overrides all other killswitches.
* The default value is true which means OnDevicePersonalization is disabled.
@@ -79,6 +81,21 @@
*/
boolean DEFAULT_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED = false;
+ /**
+ * Default value for enabling client error logging.
+ */
+ boolean DEFAULT_CLIENT_ERROR_LOGGING_ENABLED = false;
+
+ /**
+ * Default value for enabling background jobs logging.
+ */
+ boolean DEFAULT_BACKGROUND_JOBS_LOGGING_ENABLED = false;
+
+ /**
+ * Default value for background job sampling logging rate.
+ */
+ int DEFAULT_BACKGROUND_JOB_SAMPLING_LOGGING_RATE = 5;
+
String DEFAULT_CALLER_APP_ALLOW_LIST =
"android.ondevicepersonalization,"
+ "android.ondevicepersonalization.test.scenario,"
@@ -185,4 +202,8 @@
default Object getStableFlag(String flagName) {
return null;
}
+
+ default boolean getEnableClientErrorLogging() {
+ return DEFAULT_CLIENT_ERROR_LOGGING_ENABLED;
+ }
}
diff --git a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfig.java b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfig.java
index f0fcb14..3ffc631 100644
--- a/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfig.java
+++ b/src/com/android/ondevicepersonalization/services/OnDevicePersonalizationConfig.java
@@ -20,6 +20,8 @@
import com.android.ondevicepersonalization.services.download.OnDevicePersonalizationDownloadProcessingJobService;
import com.android.ondevicepersonalization.services.maintenance.OnDevicePersonalizationMaintenanceJobService;
+import java.util.Map;
+
/** Hard-coded configs for OnDevicePersonalization */
public class OnDevicePersonalizationConfig {
private OnDevicePersonalizationConfig() {}
@@ -29,34 +31,65 @@
* com.android.ondevicepersonalization.services.download.mdd.MddJobService})
*/
public static final int MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID = 1000;
+ public static final String MDD_MAINTENANCE_PERIODIC_TASK_JOB_NAME =
+ "MDD_MAINTENANCE_PERIODIC_TASK";
/**
* Job ID for Mdd Charging Periodic Task ({@link
* com.android.ondevicepersonalization.services.download.mdd.MddJobService})
*/
public static final int MDD_CHARGING_PERIODIC_TASK_JOB_ID = 1001;
+ public static final String MDD_CHARGING_PERIODIC_TASK_JOB_NAME =
+ "MDD_CHARGING_PERIODIC_TASK_JOB";
/**
* Job ID for Mdd Cellular Charging Task ({@link
* com.android.ondevicepersonalization.services.download.mdd.MddJobService})
*/
public static final int MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID = 1002;
+ public static final String MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_NAME =
+ "MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB";
/**
* Job ID for Mdd Wifi Charging Task ({@link
* com.android.ondevicepersonalization.services.download.mdd.MddJobService})
*/
public static final int MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID = 1003;
+ public static final String MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_NAME =
+ "MDD_WIFI_CHARGING_PERIODIC_TASK_JOB";
/**
* Job ID for Download Processing Task ({@link
* OnDevicePersonalizationDownloadProcessingJobService})
*/
public static final int DOWNLOAD_PROCESSING_TASK_JOB_ID = 1004;
+ public static final String DOWNLOAD_PROCESSING_TASK_JOB_NAME =
+ "DOWNLOAD_PROCESSING_TASK_JOB";
/** Job ID for Maintenance Task ({@link OnDevicePersonalizationMaintenanceJobService}) */
public static final int MAINTENANCE_TASK_JOB_ID = 1005;
+ public static final String MAINTENANCE_TASK_JOB_NAME =
+ "MAINTENANCE_TASK_JOB";
/** Job ID for User Data Collection Task ({@link UserDataCollectionJobService}) */
public static final int USER_DATA_COLLECTION_ID = 1006;
+ public static final String USER_DATA_COLLECTION_JOB_NAME =
+ "USER_DATA_COLLECTION_JOB";
+
+ public static final Map<Integer, String> JOB_ID_TO_NAME_MAP = Map.of(
+ MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID,
+ MDD_MAINTENANCE_PERIODIC_TASK_JOB_NAME,
+ MDD_CHARGING_PERIODIC_TASK_JOB_ID,
+ MDD_CHARGING_PERIODIC_TASK_JOB_NAME,
+ MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID,
+ MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_NAME,
+ MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
+ MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_NAME,
+ DOWNLOAD_PROCESSING_TASK_JOB_ID,
+ DOWNLOAD_PROCESSING_TASK_JOB_NAME,
+ MAINTENANCE_TASK_JOB_ID,
+ MAINTENANCE_TASK_JOB_NAME,
+ USER_DATA_COLLECTION_ID,
+ USER_DATA_COLLECTION_JOB_NAME
+ );
}
diff --git a/src/com/android/ondevicepersonalization/services/PhFlags.java b/src/com/android/ondevicepersonalization/services/PhFlags.java
index 9772e92..0da02ff 100644
--- a/src/com/android/ondevicepersonalization/services/PhFlags.java
+++ b/src/com/android/ondevicepersonalization/services/PhFlags.java
@@ -65,6 +65,15 @@
public static final String KEY_USER_CONSENT_CACHE_IN_MILLIS =
"user_consent_cache_duration_millis";
+ public static final String KEY_ODP_ENABLE_CLIENT_ERROR_LOGGING =
+ "odp_enable_client_error_logging";
+
+ public static final String KEY_ODP_BACKGROUND_JOBS_LOGGING_ENABLED =
+ "odp_background_jobs_logging_enabled";
+
+ public static final String KEY_ODP_BACKGROUND_JOB_SAMPLING_LOGGING_RATE =
+ "odp_background_job_sampling_logging_rate";
+
// OnDevicePersonalization Namespace String from DeviceConfig class
public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
@@ -191,30 +200,54 @@
return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(
/* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
/* name= */ KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED,
- /* defaultValue */ DEFAULT_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED);
+ /* defaultValue= */ DEFAULT_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED);
}
@Override
public String getCallerAppAllowList() {
return DeviceConfig.getString(
/* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
- /* name */ KEY_CALLER_APP_ALLOW_LIST,
- /* defaultValue */ DEFAULT_CALLER_APP_ALLOW_LIST);
+ /* name= */ KEY_CALLER_APP_ALLOW_LIST,
+ /* defaultValue= */ DEFAULT_CALLER_APP_ALLOW_LIST);
}
@Override
public String getIsolatedServiceAllowList() {
return DeviceConfig.getString(
/* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
- /* name */ KEY_ISOLATED_SERVICE_ALLOW_LIST,
- /* defaultValue */ DEFAULT_ISOLATED_SERVICE_ALLOW_LIST);
+ /* name= */ KEY_ISOLATED_SERVICE_ALLOW_LIST,
+ /* defaultValue= */ DEFAULT_ISOLATED_SERVICE_ALLOW_LIST);
}
@Override
public long getUserConsentCacheInMillis() {
return DeviceConfig.getLong(
/* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
- /* name */ KEY_USER_CONSENT_CACHE_IN_MILLIS,
- /* defaultValue */ USER_CONSENT_CACHE_IN_MILLIS);
+ /* name= */ KEY_USER_CONSENT_CACHE_IN_MILLIS,
+ /* defaultValue= */ USER_CONSENT_CACHE_IN_MILLIS);
+ }
+
+ @Override
+ public boolean getEnableClientErrorLogging() {
+ return DeviceConfig.getBoolean(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_ODP_ENABLE_CLIENT_ERROR_LOGGING,
+ /* defaultValue= */ DEFAULT_CLIENT_ERROR_LOGGING_ENABLED);
+ }
+
+ @Override
+ public boolean getBackgroundJobsLoggingEnabled() {
+ return DeviceConfig.getBoolean(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_ODP_BACKGROUND_JOBS_LOGGING_ENABLED,
+ /* defaultValue= */ DEFAULT_BACKGROUND_JOBS_LOGGING_ENABLED);
+ }
+
+ @Override
+ public int getBackgroundJobSamplingLoggingRate() {
+ return DeviceConfig.getInt(
+ /* namespace= */ NAMESPACE_ON_DEVICE_PERSONALIZATION,
+ /* name= */ KEY_ODP_BACKGROUND_JOB_SAMPLING_LOGGING_RATE,
+ /* defaultValue= */ DEFAULT_BACKGROUND_JOB_SAMPLING_LOGGING_RATE);
}
}
diff --git a/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobService.java b/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobService.java
index b5c13d7..0a19d83 100644
--- a/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobService.java
+++ b/src/com/android/ondevicepersonalization/services/data/user/UserDataCollectionJobService.java
@@ -18,6 +18,10 @@
import static android.app.job.JobScheduler.RESULT_FAILURE;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED;
+import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID;
+
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -25,11 +29,10 @@
import android.content.ComponentName;
import android.content.Context;
-
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.FlagsFactory;
-import com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
@@ -53,14 +56,14 @@
public static int schedule(Context context) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
if (jobScheduler.getPendingJob(
- OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID) != null) {
+ USER_DATA_COLLECTION_ID) != null) {
sLogger.d(TAG + ": Job is already scheduled. Doing nothing,");
return RESULT_FAILURE;
}
ComponentName serviceComponent = new ComponentName(context,
UserDataCollectionJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(
- OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID, serviceComponent);
+ USER_DATA_COLLECTION_ID, serviceComponent);
// Constraints
builder.setRequiresDeviceIdle(true);
@@ -77,12 +80,18 @@
@Override
public boolean onStartJob(JobParameters params) {
sLogger.d(TAG + ": onStartJob()");
+ OdpJobServiceLogger.getInstance(this)
+ .recordOnStartJob(USER_DATA_COLLECTION_ID);
if (FlagsFactory.getFlags().getGlobalKillSwitch()) {
sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job.");
- return cancelAndFinishJob(params);
+ return cancelAndFinishJob(params,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
}
if (!UserPrivacyStatus.getInstance().isPersonalizationStatusEnabled()) {
sLogger.d(TAG + ": Personalization is not allowed, finishing job.");
+ OdpJobServiceLogger.getInstance(this).recordJobSkipped(
+ USER_DATA_COLLECTION_ID,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED);
jobFinished(params, /* wantsReschedule = */ false);
return true;
}
@@ -107,15 +116,27 @@
@Override
public void onSuccess(Void result) {
sLogger.d(TAG + ": User data collection job completed.");
- jobFinished(params, /* wantsReschedule = */ false);
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(UserDataCollectionJobService.this)
+ .recordJobFinished(
+ USER_DATA_COLLECTION_ID,
+ /* isSuccessful= */ true,
+ wantsReschedule);
+ jobFinished(params, wantsReschedule);
}
@Override
public void onFailure(Throwable t) {
sLogger.e(TAG + ": Failed to handle JobService: " + params.getJobId(), t);
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(UserDataCollectionJobService.this)
+ .recordJobFinished(
+ USER_DATA_COLLECTION_ID,
+ /* isSuccessful= */ false,
+ wantsReschedule);
// When failure, also tell the JobScheduler that the job has completed and
// does not need to be rescheduled.
- jobFinished(params, /* wantsReschedule = */ false);
+ jobFinished(params, wantsReschedule);
}
},
OnDevicePersonalizationExecutors.getBackgroundExecutor());
@@ -129,14 +150,23 @@
mFuture.cancel(true);
}
// Reschedule the job since it ended before finishing
- return true;
+ boolean wantsReschedule = true;
+ OdpJobServiceLogger.getInstance(this)
+ .recordOnStopJob(
+ params,
+ USER_DATA_COLLECTION_ID,
+ wantsReschedule);
+ return wantsReschedule;
}
- private boolean cancelAndFinishJob(final JobParameters params) {
+ private boolean cancelAndFinishJob(final JobParameters params, int skipReason) {
JobScheduler jobScheduler = this.getSystemService(JobScheduler.class);
if (jobScheduler != null) {
- jobScheduler.cancel(OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID);
+ jobScheduler.cancel(USER_DATA_COLLECTION_ID);
}
+ OdpJobServiceLogger.getInstance(this).recordJobSkipped(
+ USER_DATA_COLLECTION_ID,
+ skipReason);
jobFinished(params, /* wantsReschedule = */ false);
return true;
}
diff --git a/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobService.java b/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobService.java
index f34ce91..0298e3d 100644
--- a/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobService.java
+++ b/src/com/android/ondevicepersonalization/services/download/OnDevicePersonalizationDownloadProcessingJobService.java
@@ -19,6 +19,9 @@
import static android.app.job.JobScheduler.RESULT_FAILURE;
import static android.content.pm.PackageManager.GET_META_DATA;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
+import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.DOWNLOAD_PROCESSING_TASK_JOB_ID;
+
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -28,13 +31,12 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.FlagsFactory;
-import com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
+import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -56,14 +58,14 @@
public static int schedule(Context context) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
if (jobScheduler.getPendingJob(
- OnDevicePersonalizationConfig.DOWNLOAD_PROCESSING_TASK_JOB_ID) != null) {
+ DOWNLOAD_PROCESSING_TASK_JOB_ID) != null) {
sLogger.d(TAG + ": Job is already scheduled. Doing nothing,");
return RESULT_FAILURE;
}
ComponentName serviceComponent = new ComponentName(context,
OnDevicePersonalizationDownloadProcessingJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(
- OnDevicePersonalizationConfig.DOWNLOAD_PROCESSING_TASK_JOB_ID, serviceComponent);
+ DOWNLOAD_PROCESSING_TASK_JOB_ID, serviceComponent);
// Constraints.
builder.setRequiresDeviceIdle(true);
@@ -77,8 +79,12 @@
@Override
public boolean onStartJob(JobParameters params) {
sLogger.d(TAG + ": onStartJob()");
+ OdpJobServiceLogger.getInstance(this).recordOnStartJob(DOWNLOAD_PROCESSING_TASK_JOB_ID);
if (FlagsFactory.getFlags().getGlobalKillSwitch()) {
sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job.");
+ OdpJobServiceLogger.getInstance(this).recordJobSkipped(
+ DOWNLOAD_PROCESSING_TASK_JOB_ID,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
jobFinished(params, /* wantsReschedule = */ false);
return true;
}
@@ -102,7 +108,14 @@
}
}
var unused = Futures.whenAllComplete(mFutures).call(() -> {
- jobFinished(params, /* wantsReschedule */ false);
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(
+ OnDevicePersonalizationDownloadProcessingJobService.this)
+ .recordJobFinished(
+ DOWNLOAD_PROCESSING_TASK_JOB_ID,
+ /* isSuccessful= */ false,
+ wantsReschedule);
+ jobFinished(params, wantsReschedule);
return null;
}, OnDevicePersonalizationExecutors.getLightweightExecutor());
@@ -117,6 +130,12 @@
}
}
// Reschedule the job since it ended before finishing
- return true;
+ boolean wantsReschedule = true;
+ OdpJobServiceLogger.getInstance(this)
+ .recordOnStopJob(
+ params,
+ DOWNLOAD_PROCESSING_TASK_JOB_ID,
+ wantsReschedule);
+ return wantsReschedule;
}
}
diff --git a/src/com/android/ondevicepersonalization/services/download/mdd/MddJobService.java b/src/com/android/ondevicepersonalization/services/download/mdd/MddJobService.java
index eab8ecf..09eb33c 100644
--- a/src/com/android/ondevicepersonalization/services/download/mdd/MddJobService.java
+++ b/src/com/android/ondevicepersonalization/services/download/mdd/MddJobService.java
@@ -16,6 +16,8 @@
package com.android.ondevicepersonalization.services.download.mdd;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED;
import static com.android.ondevicepersonalization.services.download.mdd.MddTaskScheduler.MDD_TASK_TAG_KEY;
import static com.google.android.libraries.mobiledatadownload.TaskScheduler.WIFI_CHARGING_PERIODIC_TASK;
@@ -26,12 +28,12 @@
import android.content.Context;
import android.os.PersistableBundle;
-
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.FlagsFactory;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
import com.android.ondevicepersonalization.services.download.OnDevicePersonalizationDownloadProcessingJobService;
+import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger;
import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
import com.google.common.util.concurrent.FutureCallback;
@@ -49,14 +51,19 @@
@Override
public boolean onStartJob(JobParameters params) {
+ int jobId = getMddTaskJobId(params);
sLogger.d(TAG + ": onStartJob()");
+ OdpJobServiceLogger.getInstance(this).recordOnStartJob(jobId);
if (FlagsFactory.getFlags().getGlobalKillSwitch()) {
sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job.");
- return cancelAndFinishJob(params);
+ return cancelAndFinishJob(params,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
}
if (!UserPrivacyStatus.getInstance().isPersonalizationStatusEnabled()) {
sLogger.d(TAG + ": Personalization is not allowed, finishing job.");
+ OdpJobServiceLogger.getInstance(this).recordJobSkipped(jobId,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED);
jobFinished(params, false);
return true;
}
@@ -79,17 +86,27 @@
if (WIFI_CHARGING_PERIODIC_TASK.equals(mMddTaskTag)) {
OnDevicePersonalizationDownloadProcessingJobService.schedule(context);
}
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(MddJobService.this)
+ .recordJobFinished(jobId,
+ /* isSuccessful= */ true,
+ wantsReschedule);
// Tell the JobScheduler that the job has completed and does not needs to be
// rescheduled.
- jobFinished(params, /* wantsReschedule = */ false);
+ jobFinished(params, wantsReschedule);
}
@Override
public void onFailure(Throwable t) {
- sLogger.e(TAG + ": Failed to handle JobService: " + params.getJobId(), t);
+ sLogger.e(TAG + ": Failed to handle JobService: " + jobId, t);
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(MddJobService.this)
+ .recordJobFinished(jobId,
+ /* isSuccessful= */ false,
+ wantsReschedule);
// When failure, also tell the JobScheduler that the job has completed and
// does not need to be rescheduled.
- jobFinished(params, /* wantsReschedule = */ false);
+ jobFinished(params, wantsReschedule);
}
},
OnDevicePersonalizationExecutors.getBackgroundExecutor());
@@ -104,15 +121,19 @@
OnDevicePersonalizationDownloadProcessingJobService.schedule(this);
}
// Reschedule the job since it ended before finishing
- return true;
+ boolean wantsReschedule = true;
+ OdpJobServiceLogger.getInstance(this)
+ .recordOnStopJob(params, getMddTaskJobId(params), wantsReschedule);
+ return wantsReschedule;
}
- private boolean cancelAndFinishJob(final JobParameters params) {
+ private boolean cancelAndFinishJob(final JobParameters params, int skipReason) {
JobScheduler jobScheduler = this.getSystemService(JobScheduler.class);
+ int jobId = getMddTaskJobId(params);
if (jobScheduler != null) {
- int jobId = getMddTaskJobId(params);
jobScheduler.cancel(jobId);
}
+ OdpJobServiceLogger.getInstance(this).recordJobSkipped(jobId, skipReason);
jobFinished(params, /* wantsReschedule = */ false);
return true;
}
diff --git a/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobService.java b/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobService.java
index 580a426..5ad956b 100644
--- a/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobService.java
+++ b/src/com/android/ondevicepersonalization/services/maintenance/OnDevicePersonalizationMaintenanceJobService.java
@@ -19,6 +19,10 @@
import static android.app.job.JobScheduler.RESULT_FAILURE;
import static android.content.pm.PackageManager.GET_META_DATA;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED;
+import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.MAINTENANCE_TASK_JOB_ID;
+
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -31,7 +35,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.ondevicepersonalization.internal.util.LoggerFactory;
import com.android.ondevicepersonalization.services.FlagsFactory;
-import com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig;
import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
import com.android.ondevicepersonalization.services.data.events.EventsDao;
import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
@@ -40,6 +43,7 @@
import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
+import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger;
import com.android.ondevicepersonalization.services.util.PackageUtils;
import com.google.common.util.concurrent.FutureCallback;
@@ -75,14 +79,14 @@
public static int schedule(Context context) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
if (jobScheduler.getPendingJob(
- OnDevicePersonalizationConfig.MAINTENANCE_TASK_JOB_ID) != null) {
+ MAINTENANCE_TASK_JOB_ID) != null) {
sLogger.d(TAG + ": Job is already scheduled. Doing nothing,");
return RESULT_FAILURE;
}
ComponentName serviceComponent = new ComponentName(context,
OnDevicePersonalizationMaintenanceJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(
- OnDevicePersonalizationConfig.MAINTENANCE_TASK_JOB_ID, serviceComponent);
+ MAINTENANCE_TASK_JOB_ID, serviceComponent);
// Constraints.
builder.setRequiresDeviceIdle(true);
@@ -206,12 +210,18 @@
@Override
public boolean onStartJob(JobParameters params) {
sLogger.d(TAG + ": onStartJob()");
+ OdpJobServiceLogger.getInstance(this).recordOnStartJob(
+ MAINTENANCE_TASK_JOB_ID);
if (FlagsFactory.getFlags().getGlobalKillSwitch()) {
sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job.");
- return cancelAndFinishJob(params);
+ return cancelAndFinishJob(params,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
}
if (!UserPrivacyStatus.getInstance().isPersonalizationStatusEnabled()) {
sLogger.d(TAG + ": Personalization is not allowed, finishing job.");
+ OdpJobServiceLogger.getInstance(this).recordJobSkipped(
+ MAINTENANCE_TASK_JOB_ID,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_PERSONALIZATION_NOT_ENABLED);
jobFinished(params, false);
return true;
}
@@ -234,17 +244,31 @@
@Override
public void onSuccess(Void result) {
sLogger.d(TAG + ": Maintenance job completed.");
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(
+ OnDevicePersonalizationMaintenanceJobService.this)
+ .recordJobFinished(
+ MAINTENANCE_TASK_JOB_ID,
+ /* isSuccessful= */ true,
+ wantsReschedule);
// Tell the JobScheduler that the job has completed and does not needs to be
// rescheduled.
- jobFinished(params, /* wantsReschedule = */ false);
+ jobFinished(params, wantsReschedule);
}
@Override
public void onFailure(Throwable t) {
sLogger.e(TAG + ": Failed to handle JobService: " + params.getJobId(), t);
+ boolean wantsReschedule = false;
+ OdpJobServiceLogger.getInstance(
+ OnDevicePersonalizationMaintenanceJobService.this)
+ .recordJobFinished(
+ MAINTENANCE_TASK_JOB_ID,
+ /* isSuccessful= */ false,
+ wantsReschedule);
// When failure, also tell the JobScheduler that the job has completed and
// does not need to be rescheduled.
- jobFinished(params, /* wantsReschedule = */ false);
+ jobFinished(params, wantsReschedule);
}
},
OnDevicePersonalizationExecutors.getBackgroundExecutor());
@@ -258,14 +282,23 @@
mFuture.cancel(true);
}
// Reschedule the job since it ended before finishing
- return true;
+ boolean wantsReschedule = true;
+ OdpJobServiceLogger.getInstance(this)
+ .recordOnStopJob(
+ params,
+ MAINTENANCE_TASK_JOB_ID,
+ wantsReschedule);
+ return wantsReschedule;
}
- private boolean cancelAndFinishJob(final JobParameters params) {
+ private boolean cancelAndFinishJob(final JobParameters params, int skipReason) {
JobScheduler jobScheduler = this.getSystemService(JobScheduler.class);
if (jobScheduler != null) {
- jobScheduler.cancel(OnDevicePersonalizationConfig.MAINTENANCE_TASK_JOB_ID);
+ jobScheduler.cancel(MAINTENANCE_TASK_JOB_ID);
}
+ OdpJobServiceLogger.getInstance(this).recordJobSkipped(
+ MAINTENANCE_TASK_JOB_ID,
+ skipReason);
jobFinished(params, /* wantsReschedule = */ false);
return true;
}
diff --git a/src/com/android/ondevicepersonalization/services/statsd/errorlogging/ClientErrorLogger.java b/src/com/android/ondevicepersonalization/services/statsd/errorlogging/ClientErrorLogger.java
new file mode 100644
index 0000000..c6a24f8
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/statsd/errorlogging/ClientErrorLogger.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.statsd.errorlogging;
+
+import android.annotation.NonNull;
+
+import com.android.adservices.shared.errorlogging.AbstractAdServicesErrorLogger;
+import com.android.adservices.shared.errorlogging.StatsdAdServicesErrorLogger;
+import com.android.adservices.shared.errorlogging.StatsdAdServicesErrorLoggerImpl;
+import com.android.ondevicepersonalization.services.Flags;
+import com.android.ondevicepersonalization.services.FlagsFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+public final class ClientErrorLogger extends AbstractAdServicesErrorLogger {
+ private final Flags mFlags;
+
+ private static class LazyInstanceHolder {
+ static final ClientErrorLogger LAZY_INSTANCE =
+ new ClientErrorLogger(
+ FlagsFactory.getFlags(), StatsdAdServicesErrorLoggerImpl.getInstance());
+ }
+
+ /** Returns the instance of {@link ClientErrorLogger}. */
+ @NonNull
+ public static ClientErrorLogger getInstance() {
+ return ClientErrorLogger.LazyInstanceHolder.LAZY_INSTANCE;
+ }
+
+ @VisibleForTesting
+ ClientErrorLogger(Flags flags, StatsdAdServicesErrorLogger statsdAdServicesErrorLogger) {
+ super(statsdAdServicesErrorLogger);
+ mFlags = Objects.requireNonNull(flags);
+ }
+
+ @Override
+ protected boolean isEnabled(int errorCode) {
+ return mFlags.getEnableClientErrorLogging();
+ }
+}
diff --git a/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpJobServiceLogger.java b/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpJobServiceLogger.java
new file mode 100644
index 0000000..b82bea3
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpJobServiceLogger.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.statsd.joblogging;
+
+import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.JOB_ID_TO_NAME_MAP;
+
+import android.content.Context;
+
+import com.android.adservices.shared.common.flags.ModuleSharedFlags;
+import com.android.adservices.shared.errorlogging.AdServicesErrorLogger;
+import com.android.adservices.shared.spe.logging.JobServiceLogger;
+import com.android.adservices.shared.spe.logging.StatsdJobServiceLogger;
+import com.android.adservices.shared.util.Clock;
+import com.android.internal.annotations.GuardedBy;
+import com.android.ondevicepersonalization.services.FlagsFactory;
+import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
+import com.android.ondevicepersonalization.services.statsd.errorlogging.ClientErrorLogger;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/** A background job logger to log ODP background job stats. */
+public final class OdpJobServiceLogger extends JobServiceLogger {
+ @GuardedBy("SINGLETON_LOCK")
+ private static volatile OdpJobServiceLogger sSingleton;
+
+ private static final Object SINGLETON_LOCK = new Object();
+
+ /** Create an instance of {@link JobServiceLogger}. */
+ public OdpJobServiceLogger(
+ Context context,
+ Clock clock,
+ StatsdJobServiceLogger statsdLogger,
+ AdServicesErrorLogger errorLogger,
+ Executor executor,
+ Map<Integer, String> jobIdToNameMap,
+ ModuleSharedFlags flags) {
+ super(context, clock, statsdLogger, errorLogger, executor, jobIdToNameMap, flags);
+ }
+
+ /** Get a singleton instance of {@link JobServiceLogger} to be used. */
+ public static OdpJobServiceLogger getInstance(Context context) {
+ synchronized (SINGLETON_LOCK) {
+ if (sSingleton == null) {
+ sSingleton =
+ new OdpJobServiceLogger(
+ context,
+ Clock.getInstance(),
+ new OdpStatsdJobServiceLogger(),
+ ClientErrorLogger.getInstance(),
+ OnDevicePersonalizationExecutors.getBackgroundExecutor(),
+ JOB_ID_TO_NAME_MAP,
+ FlagsFactory.getFlags());
+ }
+
+ return sSingleton;
+ }
+ }
+}
diff --git a/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpStatsdJobServiceLogger.java b/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpStatsdJobServiceLogger.java
new file mode 100644
index 0000000..4c640e1
--- /dev/null
+++ b/src/com/android/ondevicepersonalization/services/statsd/joblogging/OdpStatsdJobServiceLogger.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ondevicepersonalization.services.statsd.joblogging;
+
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__MODULE_NAME__MODULE_NAME_ON_DEVICE_PERSONALIZATION;
+
+import com.android.adservices.service.stats.AdServicesStatsLog;
+import com.android.adservices.shared.spe.logging.ExecutionReportedStats;
+import com.android.adservices.shared.spe.logging.StatsdJobServiceLogger;
+
+/** ODP implementation of {@link StatsdJobServicesLogger}. */
+public final class OdpStatsdJobServiceLogger implements StatsdJobServiceLogger {
+
+ /** Logging method for ODP background job execution stats. */
+ public void logExecutionReportedStats(ExecutionReportedStats stats) {
+ AdServicesStatsLog.write(
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED,
+ stats.getJobId(),
+ stats.getExecutionLatencyMs(),
+ stats.getExecutionPeriodMinute(),
+ stats.getExecutionResultCode(),
+ stats.getStopReason(),
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__MODULE_NAME__MODULE_NAME_ON_DEVICE_PERSONALIZATION);
+ }
+}
diff --git a/tests/manualtests/Android.bp b/tests/manualtests/Android.bp
index d42e63a..96d4956 100644
--- a/tests/manualtests/Android.bp
+++ b/tests/manualtests/Android.bp
@@ -52,6 +52,7 @@
"modules-utils-list-slice",
"owasp-java-encoder",
"tensorflowlite_java",
+ "adservices-shared-spe",
],
sdk_version: "module_current",
target_sdk_version: "current",
diff --git a/tests/servicetests/Android.bp b/tests/servicetests/Android.bp
index a6cc46f..0a8d6f5 100644
--- a/tests/servicetests/Android.bp
+++ b/tests/servicetests/Android.bp
@@ -54,6 +54,7 @@
"owasp-java-encoder",
"compatibility-device-util-axt",
"tensorflowlite_java",
+ "adservices-shared-spe",
],
sdk_version: "module_current",
target_sdk_version: "current",
diff --git a/tests/servicetests/src/com/android/ondevicepersonalization/services/download/mdd/MddJobServiceTest.java b/tests/servicetests/src/com/android/ondevicepersonalization/services/download/mdd/MddJobServiceTest.java
index d3b4436..c09b727 100644
--- a/tests/servicetests/src/com/android/ondevicepersonalization/services/download/mdd/MddJobServiceTest.java
+++ b/tests/servicetests/src/com/android/ondevicepersonalization/services/download/mdd/MddJobServiceTest.java
@@ -153,8 +153,29 @@
mUserPrivacyStatus.setPersonalizationStatusEnabled(false);
MockitoSession session = ExtendedMockito.mockitoSession().startMocking();
try {
+ JobScheduler mJobScheduler = mContext.getSystemService(JobScheduler.class);
+ PersistableBundle extras = new PersistableBundle();
+ extras.putString(MDD_TASK_TAG_KEY, WIFI_CHARGING_PERIODIC_TASK);
+ JobInfo jobInfo =
+ new JobInfo.Builder(
+ MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID,
+ new ComponentName(mContext, MddJobService.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(false)
+ .setRequiresBatteryNotLow(true)
+ .setPeriodic(21_600_000L)
+ .setPersisted(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setExtras(extras)
+ .build();
+ mJobScheduler.schedule(jobInfo);
+ assertTrue(mJobScheduler.getPendingJob(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID)
+ != null);
+ doReturn(mJobScheduler).when(mSpyService).getSystemService(JobScheduler.class);
doNothing().when(mSpyService).jobFinished(any(), anyBoolean());
- boolean result = mSpyService.onStartJob(mock(JobParameters.class));
+ JobParameters jobParameters = mock(JobParameters.class);
+ doReturn(extras).when(jobParameters).getExtras();
+ boolean result = mSpyService.onStartJob(jobParameters);
assertTrue(result);
verify(mSpyService, times(1)).jobFinished(any(), eq(false));
verify(mMockJobScheduler, times(0)).schedule(any());
@@ -213,7 +234,12 @@
MockitoSession session = ExtendedMockito.mockitoSession().strictness(
Strictness.LENIENT).startMocking();
try {
- assertTrue(mSpyService.onStopJob(mock(JobParameters.class)));
+ JobParameters jobParameters = mock(JobParameters.class);
+ PersistableBundle extras = new PersistableBundle();
+ extras.putString(MDD_TASK_TAG_KEY, WIFI_CHARGING_PERIODIC_TASK);
+ doReturn(extras).when(jobParameters).getExtras();
+
+ assertTrue(mSpyService.onStopJob(jobParameters));
verify(mMockJobScheduler, times(0)).schedule(any());
} finally {
session.finishMocking();