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.internal.os; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.ComponentName; 23 import android.content.Intent; 24 import android.os.SystemClock; 25 26 import com.android.internal.os.anr.AnrLatencyTracker; 27 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 31 /** 32 * A timeout that has triggered on the system. 33 * 34 * @hide 35 */ 36 public class TimeoutRecord { 37 /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */ 38 @IntDef(value = { 39 TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW, 40 TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE, 41 TimeoutKind.BROADCAST_RECEIVER, 42 TimeoutKind.SERVICE_START, 43 TimeoutKind.SERVICE_EXEC, 44 TimeoutKind.CONTENT_PROVIDER, 45 TimeoutKind.APP_REGISTERED, 46 TimeoutKind.SHORT_FGS_TIMEOUT, 47 TimeoutKind.JOB_SERVICE, 48 }) 49 50 @Retention(RetentionPolicy.SOURCE) 51 public @interface TimeoutKind { 52 int INPUT_DISPATCH_NO_FOCUSED_WINDOW = 1; 53 int INPUT_DISPATCH_WINDOW_UNRESPONSIVE = 2; 54 int BROADCAST_RECEIVER = 3; 55 int SERVICE_START = 4; 56 int SERVICE_EXEC = 5; 57 int CONTENT_PROVIDER = 6; 58 int APP_REGISTERED = 7; 59 int SHORT_FGS_TIMEOUT = 8; 60 int JOB_SERVICE = 9; 61 int APP_START = 10; 62 } 63 64 /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */ 65 @TimeoutKind 66 public final int mKind; 67 68 /** Reason for the timeout. */ 69 public final String mReason; 70 71 /** System uptime in millis when the timeout was triggered. */ 72 public final long mEndUptimeMillis; 73 74 /** 75 * Was the end timestamp taken right after the timeout triggered, before any potentially 76 * expensive operations such as taking locks? 77 */ 78 public final boolean mEndTakenBeforeLocks; 79 80 /** Latency tracker associated with this instance. */ 81 public final AnrLatencyTracker mLatencyTracker; 82 83 /** A handle to the timer that expired. A value of null means "no timer". */ 84 private AutoCloseable mExpiredTimer; 85 TimeoutRecord(@imeoutKind int kind, @NonNull String reason, long endUptimeMillis, boolean endTakenBeforeLocks)86 private TimeoutRecord(@TimeoutKind int kind, @NonNull String reason, long endUptimeMillis, 87 boolean endTakenBeforeLocks) { 88 this.mKind = kind; 89 this.mReason = reason; 90 this.mEndUptimeMillis = endUptimeMillis; 91 this.mEndTakenBeforeLocks = endTakenBeforeLocks; 92 this.mLatencyTracker = new AnrLatencyTracker(kind, endUptimeMillis); 93 this.mExpiredTimer = null; 94 } 95 endingNow(@imeoutKind int kind, String reason)96 private static TimeoutRecord endingNow(@TimeoutKind int kind, String reason) { 97 long endUptimeMillis = SystemClock.uptimeMillis(); 98 return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ true); 99 } 100 endingApproximatelyNow(@imeoutKind int kind, String reason)101 private static TimeoutRecord endingApproximatelyNow(@TimeoutKind int kind, String reason) { 102 long endUptimeMillis = SystemClock.uptimeMillis(); 103 return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ false); 104 } 105 106 /** Record for a broadcast receiver timeout. */ 107 @NonNull forBroadcastReceiver(@onNull Intent intent, @Nullable String packageName, @Nullable String className)108 public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent, 109 @Nullable String packageName, @Nullable String className) { 110 final Intent logIntent; 111 if (packageName != null) { 112 if (className != null) { 113 logIntent = new Intent(intent); 114 logIntent.setComponent(new ComponentName(packageName, className)); 115 } else { 116 logIntent = new Intent(intent); 117 logIntent.setPackage(packageName); 118 } 119 } else { 120 logIntent = intent; 121 } 122 return forBroadcastReceiver(logIntent); 123 } 124 125 /** Record for a broadcast receiver timeout. */ 126 @NonNull forBroadcastReceiver(@onNull Intent intent)127 public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent) { 128 final StringBuilder reason = new StringBuilder("Broadcast of "); 129 intent.toString(reason); 130 return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason.toString()); 131 } 132 133 /** Record for a broadcast receiver timeout. */ 134 @NonNull forBroadcastReceiver(@onNull Intent intent, long timeoutDurationMs)135 public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent, 136 long timeoutDurationMs) { 137 final StringBuilder reason = new StringBuilder("Broadcast of "); 138 intent.toString(reason); 139 reason.append(", waited "); 140 reason.append(timeoutDurationMs); 141 reason.append("ms"); 142 return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason.toString()); 143 } 144 145 /** Record for an input dispatch no focused window timeout */ 146 @NonNull forInputDispatchNoFocusedWindow(@onNull String reason)147 public static TimeoutRecord forInputDispatchNoFocusedWindow(@NonNull String reason) { 148 return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW, reason); 149 } 150 151 /** Record for an input dispatch window unresponsive timeout. */ 152 @NonNull forInputDispatchWindowUnresponsive(@onNull String reason)153 public static TimeoutRecord forInputDispatchWindowUnresponsive(@NonNull String reason) { 154 return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE, reason); 155 } 156 157 /** Record for a service exec timeout. */ 158 @NonNull forServiceExec(@onNull String shortInstanceName, long timeoutDurationMs)159 public static TimeoutRecord forServiceExec(@NonNull String shortInstanceName, 160 long timeoutDurationMs) { 161 String reason = 162 "executing service " + shortInstanceName + ", waited " 163 + timeoutDurationMs + "ms"; 164 return TimeoutRecord.endingNow(TimeoutKind.SERVICE_EXEC, reason); 165 } 166 167 /** Record for a service start timeout. */ 168 @NonNull forServiceStartWithEndTime(@onNull String reason, long endUptimeMillis)169 public static TimeoutRecord forServiceStartWithEndTime(@NonNull String reason, 170 long endUptimeMillis) { 171 return new TimeoutRecord(TimeoutKind.SERVICE_START, reason, 172 endUptimeMillis, /* endTakenBeforeLocks */ true); 173 } 174 175 /** Record for a content provider timeout. */ 176 @NonNull forContentProvider(@onNull String reason)177 public static TimeoutRecord forContentProvider(@NonNull String reason) { 178 return TimeoutRecord.endingApproximatelyNow(TimeoutKind.CONTENT_PROVIDER, reason); 179 } 180 181 /** Record for an app registered timeout. */ 182 @NonNull forApp(@onNull String reason)183 public static TimeoutRecord forApp(@NonNull String reason) { 184 return TimeoutRecord.endingApproximatelyNow(TimeoutKind.APP_REGISTERED, reason); 185 } 186 187 /** Record for a "short foreground service" timeout. */ 188 @NonNull forShortFgsTimeout(String reason)189 public static TimeoutRecord forShortFgsTimeout(String reason) { 190 return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason); 191 } 192 193 /** Record for a job related timeout. */ 194 @NonNull forJobService(String reason)195 public static TimeoutRecord forJobService(String reason) { 196 return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason); 197 } 198 199 /** Record for app startup timeout. */ 200 @NonNull forAppStart(String reason)201 public static TimeoutRecord forAppStart(String reason) { 202 return TimeoutRecord.endingNow(TimeoutKind.APP_START, reason); 203 } 204 205 /** Record the ID of the timer that expired. */ 206 @NonNull setExpiredTimer(@ullable AutoCloseable handle)207 public TimeoutRecord setExpiredTimer(@Nullable AutoCloseable handle) { 208 mExpiredTimer = handle; 209 return this; 210 } 211 212 /** Close the ExpiredTimer, if one is present. */ closeExpiredTimer()213 public void closeExpiredTimer() { 214 try { 215 if (mExpiredTimer != null) mExpiredTimer.close(); 216 } catch (Exception e) { 217 // mExpiredTimer.close() should never, ever throw. If it does, just rethrow as a 218 // RuntimeException. 219 throw new RuntimeException(e); 220 } 221 } 222 } 223