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.system.virtualmachine; 18 19 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; 20 21 import android.app.job.JobInfo; 22 import android.app.job.JobParameters; 23 import android.app.job.JobScheduler; 24 import android.app.job.JobService; 25 import android.content.ComponentName; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PackageManager.ApplicationInfoFlags; 29 import android.os.RemoteException; 30 import android.os.ServiceSpecificException; 31 import android.os.UserHandle; 32 import android.system.virtualizationmaintenance.IVirtualizationMaintenance; 33 import android.system.virtualizationmaintenance.IVirtualizationReconciliationCallback; 34 import android.util.Log; 35 36 import com.android.server.LocalServices; 37 import com.android.server.pm.UserManagerInternal; 38 39 import java.util.Arrays; 40 import java.util.List; 41 import java.util.concurrent.atomic.AtomicReference; 42 43 /** 44 * A job scheduler service responsible for triggering the Virtualization Service reconciliation 45 * process when scheduled. The job is scheduled to run once per day while idle and charging. 46 * 47 * <p>The reconciliation process ensures that Secretkeeper secrets belonging to apps or users that 48 * have been removed get deleted. 49 * 50 * @hide 51 */ 52 public class SecretkeeperJobService extends JobService { 53 private static final String TAG = SecretkeeperJobService.class.getName(); 54 private static final String JOBSCHEDULER_NAMESPACE = "VirtualizationSystemService"; 55 private static final int JOB_ID = 1; 56 private static final AtomicReference<SecretkeeperJob> sJob = new AtomicReference<>(); 57 scheduleJob(JobScheduler scheduler)58 static void scheduleJob(JobScheduler scheduler) { 59 try { 60 ComponentName serviceName = 61 new ComponentName("android", SecretkeeperJobService.class.getName()); 62 scheduler = scheduler.forNamespace(JOBSCHEDULER_NAMESPACE); 63 if (scheduler.schedule( 64 new JobInfo.Builder(JOB_ID, serviceName) 65 // We consume CPU and power 66 .setRequiresDeviceIdle(true) 67 .setRequiresCharging(true) 68 .setPeriodic(24 * 60 * 60 * 1000L) 69 .build()) 70 != JobScheduler.RESULT_SUCCESS) { 71 Log.e(TAG, "Unable to schedule job"); 72 return; 73 } 74 Log.i(TAG, "Scheduled job"); 75 } catch (Exception e) { 76 Log.e(TAG, "Failed to schedule job", e); 77 } 78 } 79 80 @Override onStartJob(JobParameters params)81 public boolean onStartJob(JobParameters params) { 82 Log.i(TAG, "Starting job"); 83 84 SecretkeeperJob job = new SecretkeeperJob(getPackageManager()); 85 sJob.set(job); 86 87 new Thread("SecretkeeperJob") { 88 @Override 89 public void run() { 90 try { 91 job.run(); 92 Log.i(TAG, "Job finished"); 93 } catch (Exception e) { 94 Log.e(TAG, "Job failed", e); 95 } 96 sJob.set(null); 97 // We don't reschedule on error, we will try again the next day anyway. 98 jobFinished(params, /*wantReschedule=*/ false); 99 } 100 }.start(); 101 102 return true; // Job is running in the background 103 } 104 105 @Override onStopJob(JobParameters params)106 public boolean onStopJob(JobParameters params) { 107 Log.i(TAG, "Stopping job"); 108 SecretkeeperJob job = sJob.getAndSet(null); 109 if (job != null) { 110 job.stop(); 111 } 112 return false; // Idle jobs get rescheduled anyway 113 } 114 115 private static class SecretkeeperJob { 116 private final UserManagerInternal mUserManager = 117 LocalServices.getService(UserManagerInternal.class); 118 private volatile boolean mStopRequested = false; 119 private PackageManager mPackageManager; 120 SecretkeeperJob(PackageManager packageManager)121 public SecretkeeperJob(PackageManager packageManager) { 122 mPackageManager = packageManager; 123 } 124 run()125 public void run() throws RemoteException { 126 IVirtualizationMaintenance maintenance = 127 VirtualizationSystemService.connectToMaintenanceService(); 128 maintenance.performReconciliation(new Callback()); 129 } 130 stop()131 public void stop() { 132 mStopRequested = true; 133 } 134 135 class Callback extends IVirtualizationReconciliationCallback.Stub { 136 @Override doUsersExist(int[] userIds)137 public boolean[] doUsersExist(int[] userIds) { 138 checkForStop(); 139 int[] currentUsers = mUserManager.getUserIds(); 140 boolean[] results = new boolean[userIds.length]; 141 for (int i = 0; i < userIds.length; i++) { 142 // The total number of users is likely to be small, so no need to make this 143 // better than O(N). 144 for (int user : currentUsers) { 145 if (user == userIds[i]) { 146 results[i] = true; 147 break; 148 } 149 } 150 } 151 return results; 152 } 153 154 @Override doAppsExist(int userId, int[] appIds)155 public boolean[] doAppsExist(int userId, int[] appIds) { 156 checkForStop(); 157 158 // If an app has been uninstalled but its data is still present we want to include 159 // it, since that might include a VM which will be used in the future. 160 ApplicationInfoFlags flags = ApplicationInfoFlags.of(MATCH_UNINSTALLED_PACKAGES); 161 List<ApplicationInfo> appInfos = 162 mPackageManager.getInstalledApplicationsAsUser(flags, userId); 163 int[] currentAppIds = new int[appInfos.size()]; 164 for (int i = 0; i < appInfos.size(); i++) { 165 currentAppIds[i] = UserHandle.getAppId(appInfos.get(i).uid); 166 } 167 Arrays.sort(currentAppIds); 168 169 boolean[] results = new boolean[appIds.length]; 170 for (int i = 0; i < appIds.length; i++) { 171 results[i] = Arrays.binarySearch(currentAppIds, appIds[i]) >= 0; 172 } 173 174 return results; 175 } 176 checkForStop()177 private void checkForStop() { 178 if (mStopRequested) { 179 throw new ServiceSpecificException(ERROR_STOP_REQUESTED, "Stop requested"); 180 } 181 } 182 } 183 } 184 } 185