Merge "Trigger an ANR for slow app responses."
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index cc9e517..fec5281 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -24,7 +24,9 @@
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
import android.app.Notification;
+import android.app.compat.CompatChanges;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
import android.app.job.JobInfo;
@@ -32,6 +34,9 @@
import android.app.job.JobProtoEnums;
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -57,6 +62,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -87,6 +93,15 @@
private static final boolean DEBUG = JobSchedulerService.DEBUG;
private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY;
+ /**
+ * Whether to trigger an ANR when apps are slow to respond on pre-UDC APIs and functionality.
+ */
+ @ChangeId
+ @Disabled
+ // TODO(258236856): Enable after test is fixed
+ // @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ private static final long ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES = 258236856L;
+
private static final String TAG = "JobServiceContext";
/** Amount of time the JobScheduler waits for the initial service launch+bind. */
private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
@@ -119,6 +134,7 @@
/** Used for service binding, etc. */
private final Context mContext;
private final Object mLock;
+ private final ActivityManagerInternal mActivityManagerInternal;
private final IBatteryStats mBatteryStats;
private final EconomyManagerInternal mEconomyManagerInternal;
private final JobPackageTracker mJobPackageTracker;
@@ -270,6 +286,7 @@
mContext = service.getContext();
mLock = service.getLock();
mService = service;
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mBatteryStats = batteryStats;
mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class);
mJobPackageTracker = tracker;
@@ -1121,23 +1138,31 @@
private void handleOpTimeoutLocked() {
switch (mVerb) {
case VERB_BINDING:
- Slog.w(TAG, "Time-out while trying to bind " + getRunningJobNameLocked()
- + ", dropping.");
- closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding");
+ onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
+ /* debugReason */ "timed out while binding",
+ /* anrMessage */ "Timed out while trying to bind",
+ CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
+ mRunningJob.getUid()));
break;
case VERB_STARTING:
// Client unresponsive - wedged or failed to respond in time. We don't really
// know what happened so let's log it and notify the JobScheduler
// FINISHED/NO-RETRY.
- Slog.w(TAG, "No response from client for onStartJob "
- + getRunningJobNameLocked());
- closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting");
+ onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
+ /* debugReason */ "timed out while starting",
+ /* anrMessage */ "No response to onStartJob",
+ CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
+ mRunningJob.getUid()));
break;
case VERB_STOPPING:
// At least we got somewhere, so fail but ask the JobScheduler to reschedule.
- Slog.w(TAG, "No response from client for onStopJob "
- + getRunningJobNameLocked());
- closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
+ // Don't update the stop reasons since we were already stopping the job for some
+ // other reason.
+ onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ false,
+ /* debugReason */ "timed out while stopping",
+ /* anrMessage */ "No response to onStopJob",
+ CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
+ mRunningJob.getUid()));
break;
case VERB_EXECUTING:
if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) {
@@ -1218,6 +1243,24 @@
}
}
+ @GuardedBy("mLock")
+ private void onSlowAppResponseLocked(boolean reschedule, boolean updateStopReasons,
+ @NonNull String debugReason, @NonNull String anrMessage, boolean triggerAnr) {
+ Slog.w(TAG, anrMessage + " for " + getRunningJobNameLocked());
+ if (updateStopReasons) {
+ mParams.setStopReason(
+ JobParameters.STOP_REASON_UNDEFINED,
+ JobParameters.INTERNAL_STOP_REASON_ANR,
+ debugReason);
+ }
+ if (triggerAnr) {
+ mActivityManagerInternal.appNotResponding(
+ mRunningJob.serviceProcessName, mRunningJob.getUid(),
+ TimeoutRecord.forJobService(anrMessage));
+ }
+ closeAndCleanupJobLocked(reschedule, debugReason);
+ }
+
/**
* The provided job has finished, either by calling
* {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index aa9212f..e654c38 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -509,6 +509,12 @@
public abstract void broadcastCloseSystemDialogs(String reason);
/**
+ * Trigger an ANR for the specified process.
+ */
+ public abstract void appNotResponding(@NonNull String processName, int uid,
+ @NonNull TimeoutRecord timeoutRecord);
+
+ /**
* Kills all background processes, except those matching any of the specified properties.
*
* @param minTargetSdk the target SDK version at or above which to preserve processes,
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index a587834..2f6091b 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -41,7 +41,9 @@
TimeoutKind.SERVICE_EXEC,
TimeoutKind.CONTENT_PROVIDER,
TimeoutKind.APP_REGISTERED,
- TimeoutKind.SHORT_FGS_TIMEOUT})
+ TimeoutKind.SHORT_FGS_TIMEOUT,
+ TimeoutKind.JOB_SERVICE,
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface TimeoutKind {
@@ -53,6 +55,7 @@
int CONTENT_PROVIDER = 6;
int APP_REGISTERED = 7;
int SHORT_FGS_TIMEOUT = 8;
+ int JOB_SERVICE = 9;
}
/** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -152,4 +155,10 @@
public static TimeoutRecord forShortFgsTimeout(String reason) {
return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason);
}
+
+ /** Record for a job related timeout. */
+ @NonNull
+ public static TimeoutRecord forJobService(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason);
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4d5baaf..9085e2a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6811,6 +6811,21 @@
mAnrHelper.appNotResponding(anrProcess, timeoutRecord);
}
+ private void appNotResponding(@NonNull String processName, int uid,
+ @NonNull TimeoutRecord timeoutRecord) {
+ Objects.requireNonNull(processName);
+ Objects.requireNonNull(timeoutRecord);
+
+ synchronized (this) {
+ final ProcessRecord app = getProcessRecordLocked(processName, uid);
+ if (app == null) {
+ Slog.e(TAG, "Unknown process: " + processName);
+ return;
+ }
+ mAnrHelper.appNotResponding(app, timeoutRecord);
+ }
+ }
+
void startPersistentApps(int matchFlags) {
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return;
@@ -18016,6 +18031,12 @@
}
@Override
+ public void appNotResponding(@NonNull String processName, int uid,
+ @NonNull TimeoutRecord timeoutRecord) {
+ ActivityManagerService.this.appNotResponding(processName, uid, timeoutRecord);
+ }
+
+ @Override
public void killAllBackgroundProcessesExcept(int minTargetSdk, int maxProcState) {
synchronized (ActivityManagerService.this) {
ActivityManagerService.this.killAllBackgroundProcessesExcept(