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