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.server.pm; 18 19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; 20 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; 21 22 import android.annotation.WorkerThread; 23 import android.app.ActivityManager; 24 import android.app.ActivityManager.RunningAppProcessInfo; 25 import android.app.ActivityThread; 26 import android.app.job.JobInfo; 27 import android.app.job.JobParameters; 28 import android.app.job.JobScheduler; 29 import android.app.job.JobService; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.pm.PackageInstaller.InstallConstraints; 33 import android.content.pm.PackageInstaller.InstallConstraintsResult; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.RemoteException; 37 import android.os.SystemClock; 38 import android.os.SystemProperties; 39 import android.text.format.DateUtils; 40 import android.util.Slog; 41 42 import com.android.internal.util.IndentingPrintWriter; 43 import com.android.internal.util.Preconditions; 44 45 import java.util.ArrayDeque; 46 import java.util.ArrayList; 47 import java.util.List; 48 import java.util.concurrent.CompletableFuture; 49 import java.util.concurrent.TimeUnit; 50 51 /** 52 * A helper class to coordinate install flow for sessions with install constraints. 53 * These sessions will be pending and wait until the constraints are satisfied to 54 * resume installation. 55 */ 56 public class GentleUpdateHelper { 57 private static final String TAG = "GentleUpdateHelper"; 58 private static final int JOB_ID = 235306967; // bug id 59 // The timeout used to determine whether the device is idle or not. 60 private static final long PENDING_CHECK_MILLIS = TimeUnit.SECONDS.toMillis(10); 61 62 /** 63 * A wrapper class used by JobScheduler to schedule jobs. 64 */ 65 public static class Service extends JobService { 66 @Override onStartJob(JobParameters params)67 public boolean onStartJob(JobParameters params) { 68 try { 69 var pis = (PackageInstallerService) ActivityThread.getPackageManager() 70 .getPackageInstaller(); 71 var helper = pis.getGentleUpdateHelper(); 72 helper.mHandler.post(helper::runIdleJob); 73 } catch (Exception e) { 74 Slog.e(TAG, "Failed to get PackageInstallerService", e); 75 } 76 return false; 77 } 78 79 @Override onStopJob(JobParameters params)80 public boolean onStopJob(JobParameters params) { 81 return false; 82 } 83 } 84 85 private static class PendingInstallConstraintsCheck { 86 public final List<String> packageNames; 87 public final InstallConstraints constraints; 88 public final CompletableFuture<InstallConstraintsResult> future; 89 private final long mFinishTime; 90 91 /** 92 * Note {@code timeoutMillis} will be clamped to 0 ~ one week to avoid overflow. 93 */ PendingInstallConstraintsCheck(List<String> packageNames, InstallConstraints constraints, CompletableFuture<InstallConstraintsResult> future, long timeoutMillis)94 PendingInstallConstraintsCheck(List<String> packageNames, 95 InstallConstraints constraints, 96 CompletableFuture<InstallConstraintsResult> future, 97 long timeoutMillis) { 98 this.packageNames = packageNames; 99 this.constraints = constraints; 100 this.future = future; 101 102 timeoutMillis = Math.max(0, Math.min(DateUtils.WEEK_IN_MILLIS, timeoutMillis)); 103 mFinishTime = SystemClock.elapsedRealtime() + timeoutMillis; 104 } isTimedOut()105 public boolean isTimedOut() { 106 return SystemClock.elapsedRealtime() >= mFinishTime; 107 } 108 /** 109 * The remaining time before this pending check is timed out. 110 */ getRemainingTimeMillis()111 public long getRemainingTimeMillis() { 112 long timeout = mFinishTime - SystemClock.elapsedRealtime(); 113 return Math.max(timeout, 0); 114 } 115 dump(IndentingPrintWriter pw)116 void dump(IndentingPrintWriter pw) { 117 pw.printPair("packageNames", packageNames); 118 pw.println(); 119 pw.printPair("finishTime", mFinishTime); 120 pw.println(); 121 pw.printPair("constraints notInCallRequired", constraints.isNotInCallRequired()); 122 pw.println(); 123 pw.printPair("constraints deviceIdleRequired", constraints.isDeviceIdleRequired()); 124 pw.println(); 125 pw.printPair("constraints appNotForegroundRequired", 126 constraints.isAppNotForegroundRequired()); 127 pw.println(); 128 pw.printPair("constraints appNotInteractingRequired", 129 constraints.isAppNotInteractingRequired()); 130 pw.println(); 131 pw.printPair("constraints appNotTopVisibleRequired", 132 constraints.isAppNotTopVisibleRequired()); 133 } 134 } 135 136 private final Context mContext; 137 private final Handler mHandler; 138 private final AppStateHelper mAppStateHelper; 139 // Worker thread only 140 private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>(); 141 private final ArrayList<CompletableFuture<Boolean>> mPendingIdleFutures = new ArrayList<>(); 142 private boolean mHasPendingIdleJob; 143 GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper)144 GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) { 145 mContext = context; 146 mHandler = new Handler(looper); 147 mAppStateHelper = appStateHelper; 148 } 149 systemReady()150 void systemReady() { 151 var am = mContext.getSystemService(ActivityManager.class); 152 // Monitor top-visible apps 153 am.addOnUidImportanceListener(this::onUidImportance, IMPORTANCE_FOREGROUND); 154 // Monitor foreground apps 155 am.addOnUidImportanceListener(this::onUidImportance, IMPORTANCE_FOREGROUND_SERVICE); 156 } 157 158 /** 159 * Checks if install constraints are satisfied for the given packages. 160 */ checkInstallConstraints( List<String> packageNames, InstallConstraints constraints, long timeoutMillis)161 CompletableFuture<InstallConstraintsResult> checkInstallConstraints( 162 List<String> packageNames, InstallConstraints constraints, 163 long timeoutMillis) { 164 var resultFuture = new CompletableFuture<InstallConstraintsResult>(); 165 mHandler.post(() -> { 166 var pendingCheck = new PendingInstallConstraintsCheck( 167 packageNames, constraints, resultFuture, timeoutMillis); 168 var deviceIdleFuture = constraints.isDeviceIdleRequired() 169 ? checkDeviceIdle() : CompletableFuture.completedFuture(false); 170 deviceIdleFuture.thenAccept(isIdle -> { 171 Preconditions.checkState(mHandler.getLooper().isCurrentThread()); 172 if (!processPendingCheck(pendingCheck, isIdle)) { 173 // Not resolved. Schedule a job for re-check 174 mPendingChecks.add(pendingCheck); 175 scheduleIdleJob(); 176 // Ensure the pending check is resolved after timeout, no matter constraints 177 // satisfied or not. 178 mHandler.postDelayed(() -> processPendingCheck( 179 pendingCheck, false), pendingCheck.getRemainingTimeMillis()); 180 } 181 }); 182 }); 183 return resultFuture; 184 } 185 186 /** 187 * Checks if the device is idle or not. 188 * @return A future resolved to {@code true} if the device is idle, or {@code false} if not. 189 */ 190 @WorkerThread checkDeviceIdle()191 private CompletableFuture<Boolean> checkDeviceIdle() { 192 // JobScheduler doesn't provide queries about whether the device is idle. 193 // We schedule 2 tasks here and the task which resolves 194 // the future first will determine whether the device is idle or not. 195 var future = new CompletableFuture<Boolean>(); 196 mPendingIdleFutures.add(future); 197 scheduleIdleJob(); 198 mHandler.postDelayed(() -> future.complete(false), PENDING_CHECK_MILLIS); 199 return future; 200 } 201 202 @WorkerThread scheduleIdleJob()203 private void scheduleIdleJob() { 204 // Simulate idle jobs during test. Otherwise we need to wait for 205 // more than 30 mins for JS to trigger the job. 206 boolean isIdle = SystemProperties.getBoolean("debug.pm.gentle_update_test.is_idle", false); 207 if (isIdle) { 208 mHandler.post(this::runIdleJob); 209 return; 210 } 211 212 if (mHasPendingIdleJob) { 213 // No need to schedule the job again 214 return; 215 } 216 mHasPendingIdleJob = true; 217 var componentName = new ComponentName( 218 mContext.getPackageName(), GentleUpdateHelper.Service.class.getName()); 219 var jobInfo = new JobInfo.Builder(JOB_ID, componentName) 220 .setRequiresDeviceIdle(true) 221 .build(); 222 var jobScheduler = mContext.getSystemService(JobScheduler.class); 223 jobScheduler.schedule(jobInfo); 224 } 225 226 @WorkerThread runIdleJob()227 private void runIdleJob() { 228 mHasPendingIdleJob = false; 229 processPendingChecksInIdle(); 230 231 for (var f : mPendingIdleFutures) { 232 f.complete(true); 233 } 234 mPendingIdleFutures.clear(); 235 } 236 237 @WorkerThread areConstraintsSatisfied(List<String> packageNames, InstallConstraints constraints, boolean isIdle)238 private boolean areConstraintsSatisfied(List<String> packageNames, 239 InstallConstraints constraints, boolean isIdle) { 240 return (!constraints.isDeviceIdleRequired() || isIdle) 241 && (!constraints.isAppNotForegroundRequired() 242 || !mAppStateHelper.hasForegroundApp(packageNames)) 243 && (!constraints.isAppNotInteractingRequired() 244 || !mAppStateHelper.hasInteractingApp(packageNames)) 245 && (!constraints.isAppNotTopVisibleRequired() 246 || !mAppStateHelper.hasTopVisibleApp(packageNames)) 247 && (!constraints.isNotInCallRequired() 248 || !mAppStateHelper.isInCall()); 249 } 250 251 @WorkerThread processPendingCheck( PendingInstallConstraintsCheck pendingCheck, boolean isIdle)252 private boolean processPendingCheck( 253 PendingInstallConstraintsCheck pendingCheck, boolean isIdle) { 254 var future = pendingCheck.future; 255 if (future.isDone()) { 256 return true; 257 } 258 var constraints = pendingCheck.constraints; 259 var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames); 260 var satisfied = areConstraintsSatisfied(packageNames, constraints, isIdle); 261 if (satisfied || pendingCheck.isTimedOut()) { 262 future.complete(new InstallConstraintsResult((satisfied))); 263 return true; 264 } 265 return false; 266 } 267 268 @WorkerThread processPendingChecksInIdle()269 private void processPendingChecksInIdle() { 270 int size = mPendingChecks.size(); 271 for (int i = 0; i < size; ++i) { 272 var pendingCheck = mPendingChecks.remove(); 273 if (!processPendingCheck(pendingCheck, true)) { 274 // Not resolved. Put it back in the queue. 275 mPendingChecks.add(pendingCheck); 276 } 277 } 278 if (!mPendingChecks.isEmpty()) { 279 // Schedule a job for remaining pending checks 280 scheduleIdleJob(); 281 } 282 } 283 284 @WorkerThread onUidImportance(String packageName, @RunningAppProcessInfo.Importance int importance)285 private void onUidImportance(String packageName, 286 @RunningAppProcessInfo.Importance int importance) { 287 int size = mPendingChecks.size(); 288 for (int i = 0; i < size; ++i) { 289 var pendingCheck = mPendingChecks.remove(); 290 var dependencyPackages = 291 mAppStateHelper.getDependencyPackages(pendingCheck.packageNames); 292 if (!dependencyPackages.contains(packageName) 293 || !processPendingCheck(pendingCheck, false)) { 294 mPendingChecks.add(pendingCheck); 295 } 296 } 297 if (!mPendingChecks.isEmpty()) { 298 // Schedule a job for remaining pending checks 299 scheduleIdleJob(); 300 } 301 } 302 onUidImportance(int uid, @RunningAppProcessInfo.Importance int importance)303 private void onUidImportance(int uid, @RunningAppProcessInfo.Importance int importance) { 304 var pm = ActivityThread.getPackageManager(); 305 try { 306 var packageName = pm.getNameForUid(uid); 307 mHandler.post(() -> onUidImportance(packageName, importance)); 308 } catch (RemoteException ignore) { 309 } 310 } 311 dump(IndentingPrintWriter pw)312 void dump(IndentingPrintWriter pw) { 313 pw.println("Gentle update with constraints info:"); 314 pw.increaseIndent(); 315 pw.printPair("hasPendingIdleJob", mHasPendingIdleJob); 316 pw.println(); 317 pw.printPair("Num of PendingIdleFutures", mPendingIdleFutures.size()); 318 pw.println(); 319 ArrayDeque<PendingInstallConstraintsCheck> pendingChecks = mPendingChecks.clone(); 320 int size = pendingChecks.size(); 321 pw.printPair("Num of PendingChecks", size); 322 pw.println(); 323 pw.increaseIndent(); 324 for (int i = 0; i < size; i++) { 325 pw.print(i); pw.print(":"); 326 PendingInstallConstraintsCheck pendingInstallConstraintsCheck = pendingChecks.remove(); 327 pendingInstallConstraintsCheck.dump(pw); 328 pw.println(); 329 } 330 } 331 } 332