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.ondevicepersonalization.services.maintenance;
18 
19 import static android.content.pm.PackageManager.GET_META_DATA;
20 
21 import static com.android.adservices.shared.proto.JobPolicy.BatteryType.BATTERY_TYPE_REQUIRE_NOT_LOW;
22 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
23 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_ENABLED;
24 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.MAINTENANCE_TASK_JOB_ID;
25 
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 
31 import com.android.adservices.shared.proto.JobPolicy;
32 import com.android.adservices.shared.spe.framework.ExecutionResult;
33 import com.android.adservices.shared.spe.framework.ExecutionRuntimeParameters;
34 import com.android.adservices.shared.spe.framework.JobWorker;
35 import com.android.adservices.shared.spe.scheduling.BackoffPolicy;
36 import com.android.adservices.shared.spe.scheduling.JobSpec;
37 import com.android.adservices.shared.util.Clock;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
40 import com.android.ondevicepersonalization.services.FlagsFactory;
41 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
42 import com.android.ondevicepersonalization.services.data.events.EventsDao;
43 import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
44 import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
45 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
46 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobScheduler;
47 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobServiceFactory;
48 
49 import com.google.common.util.concurrent.Futures;
50 import com.google.common.util.concurrent.ListenableFuture;
51 
52 import java.util.ArrayList;
53 
54 /** The background job to handle the OnDevicePersonalization maintenance. */
55 public final class OnDevicePersonalizationMaintenanceJob implements JobWorker {
56     @VisibleForTesting static final long PERIOD_MILLIS = 86400 * 1_000; // 24 hours
57 
58     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
59     private static final String TAG = OnDevicePersonalizationMaintenanceJob.class.getSimpleName();
60 
61     // The maximum deletion timeframe is 63 days.
62     // Set parameter to 60 days to account for job scheduler delays.
63     private static final long MAXIMUM_DELETION_TIMEFRAME_MILLIS = 5_184_000_000L;
64 
65     @Override
getExecutionFuture( Context context, ExecutionRuntimeParameters executionRuntimeParameters)66     public ListenableFuture<ExecutionResult> getExecutionFuture(
67             Context context, ExecutionRuntimeParameters executionRuntimeParameters) {
68         return Futures.submit(
69                 () -> {
70                     cleanupVendorData(context);
71 
72                     return ExecutionResult.SUCCESS;
73                 },
74                 OnDevicePersonalizationExecutors.getBackgroundExecutor());
75     }
76 
77     @Override
getJobEnablementStatus()78     public int getJobEnablementStatus() {
79         if (FlagsFactory.getFlags().getGlobalKillSwitch()) {
80             sLogger.d(
81                     TAG
82                             + ": GlobalKillSwitch enabled, skip execution of"
83                             + " OnDevicePersonalizationMaintenanceJob.");
84             return JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
85         }
86 
87         return JOB_ENABLED_STATUS_ENABLED;
88     }
89 
90     /** Schedules a unique instance of {@link OnDevicePersonalizationMaintenanceJob}. */
schedule(Context context)91     public static void schedule(Context context) {
92         // If SPE is not enabled, force to schedule the job with the old JobService.
93         if (!FlagsFactory.getFlags().getSpePilotJobEnabled()) {
94             sLogger.d(
95                     "SPE is not enabled. Schedule the job with"
96                             + " OnDevicePersonalizationMaintenanceJobService.");
97 
98             int resultCode =
99                     OnDevicePersonalizationMaintenanceJobService.schedule(
100                             context, /* forceSchedule= */ false);
101             OdpJobServiceFactory.getInstance(context)
102                     .getJobSchedulingLogger()
103                     .recordOnSchedulingLegacy(MAINTENANCE_TASK_JOB_ID, resultCode);
104 
105             return;
106         }
107 
108         OdpJobScheduler.getInstance(context).schedule(context, createDefaultJobSpec());
109     }
110 
111     @VisibleForTesting
createDefaultJobSpec()112     static JobSpec createDefaultJobSpec() {
113         JobPolicy jobPolicy =
114                 JobPolicy.newBuilder()
115                         .setJobId(MAINTENANCE_TASK_JOB_ID)
116                         .setBatteryType(BATTERY_TYPE_REQUIRE_NOT_LOW)
117                         .setRequireStorageNotLow(true)
118                         .setPeriodicJobParams(
119                                 JobPolicy.PeriodicJobParams.newBuilder()
120                                         .setPeriodicIntervalMs(PERIOD_MILLIS)
121                                         .build())
122                         .setIsPersisted(true)
123                         .build();
124 
125         BackoffPolicy backoffPolicy =
126                 new BackoffPolicy.Builder().setShouldRetryOnExecutionStop(true).build();
127 
128         return new JobSpec.Builder(jobPolicy).setBackoffPolicy(backoffPolicy).build();
129     }
130 
131     @VisibleForTesting
deleteEventsAndQueries(Context context)132     void deleteEventsAndQueries(Context context) {
133         EventsDao eventsDao = EventsDao.getInstance(context);
134         // Cleanup event and queries table.
135         eventsDao.deleteEventsAndQueries(
136                 Clock.getInstance().currentTimeMillis() - MAXIMUM_DELETION_TIMEFRAME_MILLIS);
137     }
138 
139     @VisibleForTesting
cleanupVendorData(Context context)140     void cleanupVendorData(Context context) throws Exception {
141         ArrayList<ComponentName> services = new ArrayList<>();
142 
143         for (PackageInfo packageInfo :
144                 context.getPackageManager()
145                         .getInstalledPackages(PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
146             String packageName = packageInfo.packageName;
147 
148             if (AppManifestConfigHelper.manifestContainsOdpSettings(context, packageName)) {
149                 if (!PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) {
150                     sLogger.d(TAG + ": service %s has ODP manifest, but not enrolled", packageName);
151                     continue;
152                 }
153 
154                 sLogger.d(TAG + ": service %s has ODP manifest and is enrolled", packageName);
155 
156                 String serviceClass =
157                         AppManifestConfigHelper.getServiceNameFromOdpSettings(context, packageName);
158                 ComponentName service = ComponentName.createRelative(packageName, serviceClass);
159                 services.add(service);
160             }
161         }
162 
163         OnDevicePersonalizationVendorDataDao.deleteVendorTables(context, services);
164         deleteEventsAndQueries(context);
165     }
166 }
167