blob: ebbce0227b3e858eea2effa7966a40781bcd857a [file] [log] [blame]
Filip Gruszczynski77d94482015-12-11 13:59:52 -08001package com.android.server.am;
2
Jorim Jaggi3878ca32017-02-02 17:13:05 -08003import static android.app.ActivityManager.START_SUCCESS;
4import static android.app.ActivityManager.START_TASK_TO_FRONT;
Winson Chung83471632016-12-13 11:02:12 -08005import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
Filip Gruszczynski77d94482015-12-11 13:59:52 -08006import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
7import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
8import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
Jorim Jaggi3878ca32017-02-02 17:13:05 -08009import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
Filip Gruszczynskicaae14e2015-12-16 14:40:04 -080010import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
Jorim Jaggi3878ca32017-02-02 17:13:05 -080011import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
12import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
13import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_ACTIVITY_NAME;
14import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
15import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
16import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_STARTING_WINDOW_DELAY_MS;
17import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS;
18import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_COLD_LAUNCH;
19import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_HOT_LAUNCH;
20import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
Jorim Jaggif9704102016-05-05 19:14:22 -070021import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
22import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
Filip Gruszczynski0e381e22016-01-14 16:31:33 -080023import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
Filip Gruszczynski77d94482015-12-11 13:59:52 -080024
25import android.app.ActivityManager.StackId;
26import android.content.Context;
Jorim Jaggi3878ca32017-02-02 17:13:05 -080027import android.metrics.LogMaker;
Filip Gruszczynski77d94482015-12-11 13:59:52 -080028import android.os.SystemClock;
Jorim Jaggif9704102016-05-05 19:14:22 -070029import android.util.Slog;
Jorim Jaggi3878ca32017-02-02 17:13:05 -080030import android.util.SparseArray;
31import android.util.SparseIntArray;
Filip Gruszczynski77d94482015-12-11 13:59:52 -080032
33import com.android.internal.logging.MetricsLogger;
34
Jorim Jaggi1e630c02016-05-16 12:13:13 -070035import java.util.ArrayList;
36
Filip Gruszczynski77d94482015-12-11 13:59:52 -080037/**
38 * Handles logging into Tron.
39 */
40class ActivityMetricsLogger {
Jorim Jaggif9704102016-05-05 19:14:22 -070041
42 private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityMetricsLogger" : TAG_AM;
43
Filip Gruszczynski77d94482015-12-11 13:59:52 -080044 // Window modes we are interested in logging. If we ever introduce a new type, we need to add
45 // a value here and increase the {@link #TRON_WINDOW_STATE_VARZ_STRINGS} array.
46 private static final int WINDOW_STATE_STANDARD = 0;
47 private static final int WINDOW_STATE_SIDE_BY_SIDE = 1;
48 private static final int WINDOW_STATE_FREEFORM = 2;
Winson Chung83471632016-12-13 11:02:12 -080049 private static final int WINDOW_STATE_ASSISTANT = 3;
Filip Gruszczynski77d94482015-12-11 13:59:52 -080050 private static final int WINDOW_STATE_INVALID = -1;
51
Jorim Jaggi275561a2016-02-23 10:11:02 -050052 private static final long INVALID_START_TIME = -1;
53
Filip Gruszczynski77d94482015-12-11 13:59:52 -080054 // Preallocated strings we are sending to tron, so we don't have to allocate a new one every
55 // time we log.
56 private static final String[] TRON_WINDOW_STATE_VARZ_STRINGS = {
Winson Chung83471632016-12-13 11:02:12 -080057 "window_time_0", "window_time_1", "window_time_2", "window_time_3"};
Filip Gruszczynski77d94482015-12-11 13:59:52 -080058
59 private int mWindowState = WINDOW_STATE_STANDARD;
60 private long mLastLogTimeSecs;
61 private final ActivityStackSupervisor mSupervisor;
62 private final Context mContext;
Jorim Jaggi3878ca32017-02-02 17:13:05 -080063 private final MetricsLogger mMetricsLogger = new MetricsLogger();
Filip Gruszczynski77d94482015-12-11 13:59:52 -080064
Jorim Jaggi275561a2016-02-23 10:11:02 -050065 private long mCurrentTransitionStartTime = INVALID_START_TIME;
Jorim Jaggi3878ca32017-02-02 17:13:05 -080066
67 private int mCurrentTransitionDeviceUptime;
68 private int mCurrentTransitionDelayMs;
Jorim Jaggi275561a2016-02-23 10:11:02 -050069 private boolean mLoggedTransitionStarting;
70
Jorim Jaggi3878ca32017-02-02 17:13:05 -080071 private final SparseArray<StackTransitionInfo> mStackTransitionInfo = new SparseArray<>();
72
73 private final class StackTransitionInfo {
74 private ActivityRecord launchedActivity;
75 private int startResult;
76 private boolean currentTransitionProcessRunning;
77 private int windowsDrawnDelayMs;
78 private int startingWindowDelayMs;
79 private int reason = APP_TRANSITION_TIMEOUT;
80 private boolean loggedWindowsDrawn;
81 private boolean loggedStartingWindowDrawn;
82 }
83
Filip Gruszczynski77d94482015-12-11 13:59:52 -080084 ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context) {
85 mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000;
86 mSupervisor = supervisor;
87 mContext = context;
88 }
89
90 void logWindowState() {
91 final long now = SystemClock.elapsedRealtime() / 1000;
92 if (mWindowState != WINDOW_STATE_INVALID) {
93 // We log even if the window state hasn't changed, because the user might remain in
94 // home/fullscreen move forever and we would like to track this kind of behavior
95 // too.
96 MetricsLogger.count(mContext, TRON_WINDOW_STATE_VARZ_STRINGS[mWindowState],
97 (int) (now - mLastLogTimeSecs));
98 }
99 mLastLogTimeSecs = now;
100
Filip Gruszczynski77d94482015-12-11 13:59:52 -0800101 ActivityStack stack = mSupervisor.getStack(DOCKED_STACK_ID);
Wale Ogunwale8051c5c2016-03-04 10:27:32 -0800102 if (stack != null && stack.getStackVisibilityLocked(null) != STACK_INVISIBLE) {
Filip Gruszczynski77d94482015-12-11 13:59:52 -0800103 mWindowState = WINDOW_STATE_SIDE_BY_SIDE;
Filip Gruszczynskicaae14e2015-12-16 14:40:04 -0800104 return;
Filip Gruszczynski77d94482015-12-11 13:59:52 -0800105 }
Filip Gruszczynskicaae14e2015-12-16 14:40:04 -0800106 mWindowState = WINDOW_STATE_INVALID;
107 stack = mSupervisor.getFocusedStack();
108 if (stack.mStackId == PINNED_STACK_ID) {
109 stack = mSupervisor.findStackBehind(stack);
110 }
Matthew Ngae1ff4f2016-11-10 15:49:14 -0800111 if (StackId.isHomeOrRecentsStack(stack.mStackId)
Filip Gruszczynskicaae14e2015-12-16 14:40:04 -0800112 || stack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
113 mWindowState = WINDOW_STATE_STANDARD;
114 } else if (stack.mStackId == DOCKED_STACK_ID) {
Jorim Jaggif9704102016-05-05 19:14:22 -0700115 Slog.wtf(TAG, "Docked stack shouldn't be the focused stack, because it reported not"
116 + " being visible.");
117 mWindowState = WINDOW_STATE_INVALID;
Filip Gruszczynskicaae14e2015-12-16 14:40:04 -0800118 } else if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
119 mWindowState = WINDOW_STATE_FREEFORM;
Winson Chung83471632016-12-13 11:02:12 -0800120 } else if (stack.mStackId == ASSISTANT_STACK_ID) {
121 mWindowState = WINDOW_STATE_ASSISTANT;
Filip Gruszczynskicaae14e2015-12-16 14:40:04 -0800122 } else if (StackId.isStaticStack(stack.mStackId)) {
123 throw new IllegalStateException("Unknown stack=" + stack);
Filip Gruszczynski77d94482015-12-11 13:59:52 -0800124 }
125 }
Jorim Jaggi275561a2016-02-23 10:11:02 -0500126
127 /**
128 * Notifies the tracker at the earliest possible point when we are starting to launch an
129 * activity.
130 */
131 void notifyActivityLaunching() {
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800132 if (!isAnyTransitionActive()) {
133 mCurrentTransitionStartTime = System.currentTimeMillis();
134 }
Jorim Jaggi275561a2016-02-23 10:11:02 -0500135 }
136
137 /**
Jorim Jaggi1e630c02016-05-16 12:13:13 -0700138 * Notifies the tracker that the activity is actually launching.
139 *
140 * @param resultCode one of the ActivityManager.START_* flags, indicating the result of the
141 * launch
142 * @param launchedActivity the activity that is being launched
143 */
144 void notifyActivityLaunched(int resultCode, ActivityRecord launchedActivity) {
145 final ProcessRecord processRecord = launchedActivity != null
146 ? mSupervisor.mService.mProcessNames.get(launchedActivity.processName,
147 launchedActivity.appInfo.uid)
148 : null;
149 final boolean processRunning = processRecord != null;
Jorim Jaggi1e630c02016-05-16 12:13:13 -0700150
151 // We consider this a "process switch" if the process of the activity that gets launched
152 // didn't have an activity that was in started state. In this case, we assume that lot
153 // of caches might be purged so the time until it produces the first frame is very
154 // interesting.
155 final boolean processSwitch = processRecord == null
156 || !hasStartedActivity(processRecord, launchedActivity);
157
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800158 notifyActivityLaunched(resultCode, launchedActivity, processRunning, processSwitch);
Jorim Jaggi1e630c02016-05-16 12:13:13 -0700159 }
160
161 private boolean hasStartedActivity(ProcessRecord record, ActivityRecord launchedActivity) {
162 final ArrayList<ActivityRecord> activities = record.activities;
163 for (int i = activities.size() - 1; i >= 0; i--) {
164 final ActivityRecord activity = activities.get(i);
165 if (launchedActivity == activity) {
166 continue;
167 }
168 if (!activity.stopped) {
169 return true;
170 }
171 }
172 return false;
173 }
174
175 /**
Jorim Jaggi275561a2016-02-23 10:11:02 -0500176 * Notifies the tracker the the activity is actually launching.
177 *
178 * @param resultCode one of the ActivityManager.START_* flags, indicating the result of the
179 * launch
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800180 * @param launchedActivity the activity being launched
Jorim Jaggi275561a2016-02-23 10:11:02 -0500181 * @param processRunning whether the process that will contains the activity is already running
Jorim Jaggibe67c902016-04-12 00:53:16 -0700182 * @param processSwitch whether the process that will contain the activity didn't have any
183 * activity that was stopped, i.e. the started activity is "switching"
184 * processes
Jorim Jaggi275561a2016-02-23 10:11:02 -0500185 */
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800186 private void notifyActivityLaunched(int resultCode, ActivityRecord launchedActivity,
Jorim Jaggibe67c902016-04-12 00:53:16 -0700187 boolean processRunning, boolean processSwitch) {
Jorim Jaggi275561a2016-02-23 10:11:02 -0500188
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800189 // If we are already in an existing transition, only update the activity name, but not the
190 // other attributes.
191 final int stackId = launchedActivity != null && launchedActivity.getStack() != null
192 ? launchedActivity.getStack().mStackId
193 : INVALID_STACK_ID;
194 final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
195 if (launchedActivity != null && info != null) {
196 info.launchedActivity = launchedActivity;
Jorim Jaggi275561a2016-02-23 10:11:02 -0500197 return;
198 }
199
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800200 final boolean otherStacksLaunching = mStackTransitionInfo.size() > 0 && info == null;
201 if ((resultCode < 0 || launchedActivity == null || !processSwitch
202 || stackId == INVALID_STACK_ID) && !otherStacksLaunching) {
Alison Cichowlas803054d2016-12-13 14:38:01 -0500203
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800204 // Failed to launch or it was not a process switch, so we don't care about the timing.
205 reset(true /* abort */);
206 return;
207 } else if (otherStacksLaunching) {
208 // Don't log this stack but continue with the other stacks.
209 return;
210 }
211
212 final StackTransitionInfo newInfo = new StackTransitionInfo();
213 newInfo.launchedActivity = launchedActivity;
214 newInfo.currentTransitionProcessRunning = processRunning;
215 newInfo.startResult = resultCode;
216 mStackTransitionInfo.append(stackId, newInfo);
217 mCurrentTransitionDeviceUptime = (int) (SystemClock.uptimeMillis() / 1000);
Jorim Jaggi275561a2016-02-23 10:11:02 -0500218 }
219
220 /**
221 * Notifies the tracker that all windows of the app have been drawn.
222 */
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800223 void notifyWindowsDrawn(int stackId) {
224 final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
225 if (info == null || info.loggedWindowsDrawn) {
Jorim Jaggi275561a2016-02-23 10:11:02 -0500226 return;
227 }
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800228 info.windowsDrawnDelayMs = calculateCurrentDelay();
229 info.loggedWindowsDrawn = true;
230 if (allStacksWindowsDrawn() && mLoggedTransitionStarting) {
231 reset(false /* abort */);
Jorim Jaggi275561a2016-02-23 10:11:02 -0500232 }
233 }
234
235 /**
236 * Notifies the tracker that the starting window was drawn.
237 */
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800238 void notifyStartingWindowDrawn(int stackId) {
239 final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
240 if (info == null || info.loggedStartingWindowDrawn) {
Jorim Jaggi275561a2016-02-23 10:11:02 -0500241 return;
242 }
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800243 info.loggedStartingWindowDrawn = true;
244 info.startingWindowDelayMs = calculateCurrentDelay();
Jorim Jaggi275561a2016-02-23 10:11:02 -0500245 }
246
247 /**
248 * Notifies the tracker that the app transition is starting.
249 *
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800250 * @param stackIdReasons A map from stack id to a reason integer, which must be on of
251 * ActivityManagerInternal.APP_TRANSITION_* reasons.
Jorim Jaggi275561a2016-02-23 10:11:02 -0500252 */
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800253 void notifyTransitionStarting(SparseIntArray stackIdReasons) {
254 if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
Jorim Jaggi275561a2016-02-23 10:11:02 -0500255 return;
256 }
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800257 mCurrentTransitionDelayMs = calculateCurrentDelay();
Jorim Jaggi275561a2016-02-23 10:11:02 -0500258 mLoggedTransitionStarting = true;
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800259 for (int index = stackIdReasons.size() - 1; index >= 0; index--) {
260 final int stackId = stackIdReasons.keyAt(index);
261 final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
262 if (info == null) {
263 continue;
264 }
265 info.reason = stackIdReasons.valueAt(index);
266 }
267 if (allStacksWindowsDrawn()) {
268 reset(false /* abort */);
Jorim Jaggi275561a2016-02-23 10:11:02 -0500269 }
270 }
271
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800272 private boolean allStacksWindowsDrawn() {
273 for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
274 if (!mStackTransitionInfo.valueAt(index).loggedWindowsDrawn) {
275 return false;
276 }
277 }
278 return true;
Jorim Jaggi275561a2016-02-23 10:11:02 -0500279 }
280
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800281 private boolean isAnyTransitionActive() {
282 return mCurrentTransitionStartTime != INVALID_START_TIME
283 && mStackTransitionInfo.size() > 0;
284 }
285
286 private void reset(boolean abort) {
287 if (!abort && isAnyTransitionActive()) {
288 logAppTransitionMultiEvents();
289 }
Jorim Jaggi275561a2016-02-23 10:11:02 -0500290 mCurrentTransitionStartTime = INVALID_START_TIME;
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800291 mCurrentTransitionDelayMs = -1;
Jorim Jaggi275561a2016-02-23 10:11:02 -0500292 mLoggedTransitionStarting = false;
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800293 mStackTransitionInfo.clear();
Jorim Jaggi275561a2016-02-23 10:11:02 -0500294 }
295
296 private int calculateCurrentDelay() {
297
298 // Shouldn't take more than 25 days to launch an app, so int is fine here.
299 return (int) (System.currentTimeMillis() - mCurrentTransitionStartTime);
300 }
Jorim Jaggi3878ca32017-02-02 17:13:05 -0800301
302 private void logAppTransitionMultiEvents() {
303 for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
304 final StackTransitionInfo info = mStackTransitionInfo.valueAt(index);
305 final int type = getTransitionType(info);
306 if (type == -1) {
307 return;
308 }
309 final LogMaker builder = new LogMaker(APP_TRANSITION);
310 builder.setPackageName(info.launchedActivity.packageName);
311 builder.addTaggedData(APP_TRANSITION_ACTIVITY_NAME, info.launchedActivity.info.name);
312 builder.setType(type);
313 builder.addTaggedData(APP_TRANSITION_DEVICE_UPTIME_SECONDS,
314 mCurrentTransitionDeviceUptime);
315 builder.addTaggedData(APP_TRANSITION_DELAY_MS, mCurrentTransitionDelayMs);
316 builder.setSubtype(info.reason);
317 if (info.startingWindowDelayMs != -1) {
318 builder.addTaggedData(APP_TRANSITION_STARTING_WINDOW_DELAY_MS,
319 info.startingWindowDelayMs);
320 }
321 builder.addTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, info.windowsDrawnDelayMs);
322 mMetricsLogger.write(builder);
323 }
324 }
325
326 private int getTransitionType(StackTransitionInfo info) {
327 if (info.currentTransitionProcessRunning) {
328 if (info.startResult == START_SUCCESS) {
329 return TYPE_TRANSITION_WARM_LAUNCH;
330 } else if (info.startResult == START_TASK_TO_FRONT) {
331 return TYPE_TRANSITION_HOT_LAUNCH;
332 }
333 } else if (info.startResult == START_SUCCESS) {
334 return TYPE_TRANSITION_COLD_LAUNCH;
335 }
336 return -1;
337 }
Filip Gruszczynski77d94482015-12-11 13:59:52 -0800338}