1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.internal.util;
16
17import android.content.BroadcastReceiver;
18import android.content.Context;
19import android.content.Intent;
20import android.content.IntentFilter;
21import android.os.Build;
22import android.os.SystemClock;
23import android.os.SystemProperties;
24import android.os.Trace;
25import android.util.EventLog;
26import android.util.Log;
27import android.util.SparseLongArray;
28
29import com.android.internal.logging.EventLogTags;
30
31/**
32 * Class to track various latencies in SystemUI. It then outputs the latency to logcat so these
33 * latencies can be captured by tests and then used for dashboards.
34 * <p>
35 * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but
36 * eventually we'd want to merge these two packages together so Keyguard can use common classes
37 * that are shared with SystemUI.
38 */
39public class LatencyTracker {
40
41    private static final String ACTION_RELOAD_PROPERTY =
42            "com.android.systemui.RELOAD_LATENCY_TRACKER_PROPERTY";
43
44    private static final String TAG = "LatencyTracker";
45
46    /**
47     * Time it takes until the first frame of the notification panel to be displayed while expanding
48     */
49    public static final int ACTION_EXPAND_PANEL = 0;
50
51    /**
52     * Time it takes until the first frame of recents is drawn after invoking it with the button.
53     */
54    public static final int ACTION_TOGGLE_RECENTS = 1;
55
56    /**
57     * Time between we get a fingerprint acquired signal until we start with the unlock animation
58     */
59    public static final int ACTION_FINGERPRINT_WAKE_AND_UNLOCK = 2;
60
61    /**
62     * Time it takes to check PIN/Pattern/Password.
63     */
64    public static final int ACTION_CHECK_CREDENTIAL = 3;
65
66    /**
67     * Time it takes to check fully PIN/Pattern/Password, i.e. that's the time spent including the
68     * actions to unlock a user.
69     */
70    public static final int ACTION_CHECK_CREDENTIAL_UNLOCKED = 4;
71
72    /**
73     * Time it takes to turn on the screen.
74     */
75    public static final int ACTION_TURN_ON_SCREEN = 5;
76
77    /**
78     * Time it takes to rotate the screen.
79     */
80    public static final int ACTION_ROTATE_SCREEN = 6;
81
82    private static final String[] NAMES = new String[] {
83            "expand panel",
84            "toggle recents",
85            "fingerprint wake-and-unlock",
86            "check credential",
87            "check credential unlocked",
88            "turn on screen",
89            "rotate the screen"};
90
91    private static LatencyTracker sLatencyTracker;
92
93    private final SparseLongArray mStartRtc = new SparseLongArray();
94    private boolean mEnabled;
95
96    public static LatencyTracker getInstance(Context context) {
97        if (sLatencyTracker == null) {
98            sLatencyTracker = new LatencyTracker(context);
99        }
100        return sLatencyTracker;
101    }
102
103    private LatencyTracker(Context context) {
104        context.registerReceiver(new BroadcastReceiver() {
105            @Override
106            public void onReceive(Context context, Intent intent) {
107                reloadProperty();
108            }
109        }, new IntentFilter(ACTION_RELOAD_PROPERTY));
110        reloadProperty();
111    }
112
113    private void reloadProperty() {
114        mEnabled = SystemProperties.getBoolean("debug.systemui.latency_tracking", false);
115    }
116
117    public static boolean isEnabled(Context ctx) {
118        return Build.IS_DEBUGGABLE && getInstance(ctx).mEnabled;
119    }
120
121    /**
122     * Notifies that an action is starting. This needs to be called from the main thread.
123     *
124     * @param action The action to start. One of the ACTION_* values.
125     */
126    public void onActionStart(int action) {
127        if (!mEnabled) {
128            return;
129        }
130        Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, NAMES[action], 0);
131        mStartRtc.put(action, SystemClock.elapsedRealtime());
132    }
133
134    /**
135     * Notifies that an action has ended. This needs to be called from the main thread.
136     *
137     * @param action The action to end. One of the ACTION_* values.
138     */
139    public void onActionEnd(int action) {
140        if (!mEnabled) {
141            return;
142        }
143        long endRtc = SystemClock.elapsedRealtime();
144        long startRtc = mStartRtc.get(action, -1);
145        if (startRtc == -1) {
146            return;
147        }
148        mStartRtc.delete(action);
149        Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, NAMES[action], 0);
150        logAction(action, (int)(endRtc - startRtc));
151    }
152
153    /**
154     * Logs an action that has started and ended. This needs to be called from the main thread.
155     *
156     * @param action The action to end. One of the ACTION_* values.
157     * @param duration The duration of the action in ms.
158     */
159    public static void logAction(int action, int duration) {
160        Log.i(TAG, "action=" + action + " latency=" + duration);
161        EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, duration);
162    }
163}
164