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