• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.maintenance;
18 
19 import static android.app.job.JobScheduler.RESULT_FAILURE;
20 import static android.content.pm.PackageManager.GET_META_DATA;
21 
22 import android.app.job.JobInfo;
23 import android.app.job.JobParameters;
24 import android.app.job.JobScheduler;
25 import android.app.job.JobService;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.util.Log;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig;
34 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
35 import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
36 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
37 import com.android.ondevicepersonalization.services.util.PackageUtils;
38 
39 import com.google.common.util.concurrent.FutureCallback;
40 import com.google.common.util.concurrent.Futures;
41 import com.google.common.util.concurrent.ListenableFuture;
42 
43 import java.util.AbstractMap;
44 import java.util.HashSet;
45 import java.util.Map;
46 import java.util.Set;
47 
48 /**
49  * JobService to handle the OnDevicePersonalization maintenance
50  */
51 public class OnDevicePersonalizationMaintenanceJobService extends JobService {
52     public static final String TAG = "OnDevicePersonalizationMaintenanceJobService";
53     private static final long PERIOD_SECONDS = 86400;
54     private ListenableFuture<Void> mFuture;
55 
56     /**
57      * Schedules a unique instance of OnDevicePersonalizationMaintenanceJobService to be run.
58      */
schedule(Context context)59     public static int schedule(Context context) {
60         JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
61         if (jobScheduler.getPendingJob(
62                 OnDevicePersonalizationConfig.MAINTENANCE_TASK_JOB_ID) != null) {
63             Log.d(TAG, "Job is already scheduled. Doing nothing,");
64             return RESULT_FAILURE;
65         }
66         ComponentName serviceComponent = new ComponentName(context,
67                 OnDevicePersonalizationMaintenanceJobService.class);
68         JobInfo.Builder builder = new JobInfo.Builder(
69                 OnDevicePersonalizationConfig.MAINTENANCE_TASK_JOB_ID, serviceComponent);
70 
71         // Constraints.
72         builder.setRequiresDeviceIdle(true);
73         builder.setRequiresBatteryNotLow(true);
74         builder.setRequiresStorageNotLow(true);
75         builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE);
76         builder.setPeriodic(1000 * PERIOD_SECONDS); // JobScheduler uses Milliseconds.
77         // persist this job across boots
78         builder.setPersisted(true);
79 
80         return jobScheduler.schedule(builder.build());
81     }
82 
83     @Override
onStartJob(JobParameters params)84     public boolean onStartJob(JobParameters params) {
85         Log.d(TAG, "onStartJob()");
86         Context context = this;
87         mFuture = Futures.submit(new Runnable() {
88             @Override
89             public void run() {
90                 Log.d(TAG, "Running maintenance job");
91                 try {
92                     cleanupVendorData(context);
93                 } catch (Exception e) {
94                     Log.e(TAG, "Failed to cleanup vendorData", e);
95                 }
96             }
97         }, OnDevicePersonalizationExecutors.getBackgroundExecutor());
98 
99         Futures.addCallback(
100                 mFuture,
101                 new FutureCallback<Void>() {
102                     @Override
103                     public void onSuccess(Void result) {
104                         Log.d(TAG, "Maintenance job completed.");
105                         // Tell the JobScheduler that the job has completed and does not needs to be
106                         // rescheduled.
107                         jobFinished(params, /* wantsReschedule = */ false);
108                     }
109 
110                     @Override
111                     public void onFailure(Throwable t) {
112                         Log.e(TAG, "Failed to handle JobService: " + params.getJobId(), t);
113                         //  When failure, also tell the JobScheduler that the job has completed and
114                         // does not need to be rescheduled.
115                         jobFinished(params, /* wantsReschedule = */ false);
116                     }
117                 },
118                 OnDevicePersonalizationExecutors.getBackgroundExecutor());
119 
120         return true;
121     }
122 
123     @Override
onStopJob(JobParameters params)124     public boolean onStopJob(JobParameters params) {
125         if (mFuture != null) {
126             mFuture.cancel(true);
127         }
128         // Reschedule the job since it ended before finishing
129         return true;
130     }
131 
132     @VisibleForTesting
cleanupVendorData(Context context)133     static void cleanupVendorData(Context context) throws Exception {
134         Set<Map.Entry<String, String>> vendors = new HashSet<>(
135                 OnDevicePersonalizationVendorDataDao.getVendors(context));
136 
137         // Remove all valid packages from the set
138         for (PackageInfo packageInfo : context.getPackageManager().getInstalledPackages(
139                 PackageManager.PackageInfoFlags.of(GET_META_DATA))) {
140             String packageName = packageInfo.packageName;
141             if (AppManifestConfigHelper.manifestContainsOdpSettings(
142                     context, packageName)) {
143                 vendors.remove(new AbstractMap.SimpleImmutableEntry<>(packageName,
144                         PackageUtils.getCertDigest(context, packageName)));
145             }
146         }
147 
148         Log.d(TAG, "Deleting: " + vendors.toString());
149         // Delete the remaining tables for packages not found onboarded
150         for (Map.Entry<String, String> entry : vendors) {
151             OnDevicePersonalizationVendorDataDao.deleteVendorData(context, entry.getKey(),
152                     entry.getValue());
153         }
154 
155     }
156 }
157