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;
18 
19 import static android.app.job.JobScheduler.RESULT_FAILURE;
20 import static android.content.pm.PackageManager.GET_META_DATA;
21 
22 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
23 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.DOWNLOAD_PROCESSING_TASK_JOB_ID;
24 
25 import android.app.job.JobInfo;
26 import android.app.job.JobParameters;
27 import android.app.job.JobScheduler;
28 import android.app.job.JobService;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.pm.PackageInfo;
32 import android.content.pm.PackageManager;
33 
34 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
35 import com.android.ondevicepersonalization.services.FlagsFactory;
36 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
37 import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker;
38 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
39 import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger;
40 
41 import com.google.common.util.concurrent.Futures;
42 import com.google.common.util.concurrent.ListenableFuture;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /**
48  * JobService to handle the processing of the downloaded vendor data
49  */
50 public class OnDevicePersonalizationDownloadProcessingJobService extends JobService {
51     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
52     private static final String TAG = "OnDevicePersonalizationDownloadProcessingJobService";
53     private List<ListenableFuture<Void>> mFutures;
54 
55     /**
56      * Schedules a unique instance of OnDevicePersonalizationDownloadProcessingJobService to be run.
57      */
schedule(Context context)58     public static int schedule(Context context) {
59         JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
60         if (jobScheduler.getPendingJob(
61                 DOWNLOAD_PROCESSING_TASK_JOB_ID) != null) {
62             sLogger.d(TAG + ": Job is already scheduled. Doing nothing,");
63             return RESULT_FAILURE;
64         }
65         ComponentName serviceComponent = new ComponentName(context,
66                 OnDevicePersonalizationDownloadProcessingJobService.class);
67         JobInfo.Builder builder = new JobInfo.Builder(
68                 DOWNLOAD_PROCESSING_TASK_JOB_ID, serviceComponent);
69 
70         // Constraints.
71         builder.setRequiresDeviceIdle(true);
72         builder.setRequiresBatteryNotLow(true);
73         builder.setRequiresStorageNotLow(true);
74         builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE);
75         builder.setPersisted(true);
76 
77         return jobScheduler.schedule(builder.build());
78     }
79 
80     @Override
onStartJob(JobParameters params)81     public boolean onStartJob(JobParameters params) {
82         sLogger.d(TAG + ": onStartJob()");
83         OdpJobServiceLogger.getInstance(this).recordOnStartJob(DOWNLOAD_PROCESSING_TASK_JOB_ID);
84         if (FlagsFactory.getFlags().getGlobalKillSwitch()) {
85             sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job.");
86             OdpJobServiceLogger.getInstance(this).recordJobSkipped(
87                     DOWNLOAD_PROCESSING_TASK_JOB_ID,
88                     AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
89             jobFinished(params, /* wantsReschedule = */ false);
90             return true;
91         }
92 
93         mFutures = new ArrayList<>();
94         for (PackageInfo packageInfo : this.getPackageManager().getInstalledPackages(
95                 PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
96             String packageName = packageInfo.packageName;
97             if (AppManifestConfigHelper.manifestContainsOdpSettings(
98                     this, packageName)) {
99                 if (!PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) {
100                     sLogger.d(TAG + ": service %s has ODP manifest, but not enrolled",
101                             packageName);
102                     continue;
103                 }
104                 sLogger.d(TAG + ": service %s has ODP manifest and is enrolled", packageName);
105                 mFutures.add(Futures.submitAsync(
106                         new OnDevicePersonalizationDataProcessingAsyncCallable(packageName,
107                                 this),
108                         OnDevicePersonalizationExecutors.getBackgroundExecutor()));
109             }
110         }
111         var unused = Futures.whenAllComplete(mFutures).call(() -> {
112             boolean wantsReschedule = false;
113             boolean allSuccess = true;
114             for (ListenableFuture<Void> future : mFutures) {
115                 try {
116                     future.get();
117                 } catch (Exception e) {
118                     allSuccess = false;
119                     break;
120                 }
121             }
122             OdpJobServiceLogger.getInstance(
123                     OnDevicePersonalizationDownloadProcessingJobService.this)
124                     .recordJobFinished(
125                             DOWNLOAD_PROCESSING_TASK_JOB_ID,
126                             /* isSuccessful= */ allSuccess,
127                             wantsReschedule);
128             jobFinished(params, wantsReschedule);
129             return null;
130         }, OnDevicePersonalizationExecutors.getLightweightExecutor());
131 
132         return true;
133     }
134 
135     @Override
onStopJob(JobParameters params)136     public boolean onStopJob(JobParameters params) {
137         if (mFutures != null) {
138             for (ListenableFuture<Void> f : mFutures) {
139                 f.cancel(true);
140             }
141         }
142         // Reschedule the job since it ended before finishing
143         boolean wantsReschedule = true;
144         OdpJobServiceLogger.getInstance(this)
145                 .recordOnStopJob(
146                         params,
147                         DOWNLOAD_PROCESSING_TASK_JOB_ID,
148                         wantsReschedule);
149         return wantsReschedule;
150     }
151 }
152