1/*
2 * Copyright (C) 2014 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
17package com.android.systemui.doze;
18
19import android.content.Context;
20import android.os.Build;
21import android.util.Log;
22import android.util.TimeUtils;
23
24import com.android.keyguard.KeyguardUpdateMonitor;
25import com.android.keyguard.KeyguardUpdateMonitorCallback;
26
27import java.io.PrintWriter;
28import java.text.SimpleDateFormat;
29import java.util.Date;
30
31public class DozeLog {
32    private static final String TAG = "DozeLog";
33    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
34    private static final boolean ENABLED = true;
35    private static final int SIZE = Build.IS_DEBUGGABLE ? 400 : 50;
36    static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
37
38    private static final int PULSE_REASONS = 5;
39
40    public static final int PULSE_REASON_NONE = -1;
41    public static final int PULSE_REASON_INTENT = 0;
42    public static final int PULSE_REASON_NOTIFICATION = 1;
43    public static final int PULSE_REASON_SENSOR_SIGMOTION = 2;
44    public static final int PULSE_REASON_SENSOR_PICKUP = 3;
45    public static final int PULSE_REASON_SENSOR_DOUBLE_TAP = 4;
46
47    private static boolean sRegisterKeyguardCallback = true;
48
49    private static long[] sTimes;
50    private static String[] sMessages;
51    private static int sPosition;
52    private static int sCount;
53    private static boolean sPulsing;
54
55    private static long sSince;
56    private static SummaryStats sPickupPulseNearVibrationStats;
57    private static SummaryStats sPickupPulseNotNearVibrationStats;
58    private static SummaryStats sNotificationPulseStats;
59    private static SummaryStats sScreenOnPulsingStats;
60    private static SummaryStats sScreenOnNotPulsingStats;
61    private static SummaryStats sEmergencyCallStats;
62    private static SummaryStats[][] sProxStats; // [reason][near/far]
63
64    public static void tracePickupPulse(Context context, boolean withinVibrationThreshold) {
65        if (!ENABLED) return;
66        init(context);
67        log("pickupPulse withinVibrationThreshold=" + withinVibrationThreshold);
68        (withinVibrationThreshold ? sPickupPulseNearVibrationStats
69                : sPickupPulseNotNearVibrationStats).append();
70    }
71
72    public static void tracePulseStart(int reason) {
73        if (!ENABLED) return;
74        sPulsing = true;
75        log("pulseStart reason=" + pulseReasonToString(reason));
76    }
77
78    public static void tracePulseFinish() {
79        if (!ENABLED) return;
80        sPulsing = false;
81        log("pulseFinish");
82    }
83
84    public static void traceNotificationPulse(Context context) {
85        if (!ENABLED) return;
86        init(context);
87        log("notificationPulse");
88        sNotificationPulseStats.append();
89    }
90
91    private static void init(Context context) {
92        synchronized (DozeLog.class) {
93            if (sMessages == null) {
94                sTimes = new long[SIZE];
95                sMessages = new String[SIZE];
96                sSince = System.currentTimeMillis();
97                sPickupPulseNearVibrationStats = new SummaryStats();
98                sPickupPulseNotNearVibrationStats = new SummaryStats();
99                sNotificationPulseStats = new SummaryStats();
100                sScreenOnPulsingStats = new SummaryStats();
101                sScreenOnNotPulsingStats = new SummaryStats();
102                sEmergencyCallStats = new SummaryStats();
103                sProxStats = new SummaryStats[PULSE_REASONS][2];
104                for (int i = 0; i < PULSE_REASONS; i++) {
105                    sProxStats[i][0] = new SummaryStats();
106                    sProxStats[i][1] = new SummaryStats();
107                }
108                log("init");
109                if (sRegisterKeyguardCallback) {
110                    KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback);
111                }
112            }
113        }
114    }
115
116    public static void traceDozing(Context context, boolean dozing) {
117        if (!ENABLED) return;
118        sPulsing = false;
119        init(context);
120        log("dozing " + dozing);
121    }
122
123    public static void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
124            boolean screenOnFromTouch) {
125        if (!ENABLED) return;
126        log("fling expand=" + expand + " aboveThreshold=" + aboveThreshold + " thresholdNeeded="
127                + thresholdNeeded + " screenOnFromTouch=" + screenOnFromTouch);
128    }
129
130    public static void traceEmergencyCall() {
131        if (!ENABLED) return;
132        log("emergencyCall");
133        sEmergencyCallStats.append();
134    }
135
136    public static void traceKeyguardBouncerChanged(boolean showing) {
137        if (!ENABLED) return;
138        log("bouncer " + showing);
139    }
140
141    public static void traceScreenOn() {
142        if (!ENABLED) return;
143        log("screenOn pulsing=" + sPulsing);
144        (sPulsing ? sScreenOnPulsingStats : sScreenOnNotPulsingStats).append();
145        sPulsing = false;
146    }
147
148    public static void traceScreenOff(int why) {
149        if (!ENABLED) return;
150        log("screenOff why=" + why);
151    }
152
153    public static void traceMissedTick(String delay) {
154        if (!ENABLED) return;
155        log("missedTick by=" + delay);
156    }
157
158    public static void traceKeyguard(boolean showing) {
159        if (!ENABLED) return;
160        log("keyguard " + showing);
161        if (!showing) {
162            sPulsing = false;
163        }
164    }
165
166    public static void traceProximityResult(Context context, boolean near, long millis,
167            int pulseReason) {
168        if (!ENABLED) return;
169        init(context);
170        log("proximityResult reason=" + pulseReasonToString(pulseReason) + " near=" + near
171                + " millis=" + millis);
172        sProxStats[pulseReason][near ? 0 : 1].append();
173    }
174
175    public static String pulseReasonToString(int pulseReason) {
176        switch (pulseReason) {
177            case PULSE_REASON_INTENT: return "intent";
178            case PULSE_REASON_NOTIFICATION: return "notification";
179            case PULSE_REASON_SENSOR_SIGMOTION: return "sigmotion";
180            case PULSE_REASON_SENSOR_PICKUP: return "pickup";
181            case PULSE_REASON_SENSOR_DOUBLE_TAP: return "doubletap";
182            default: throw new IllegalArgumentException("bad reason: " + pulseReason);
183        }
184    }
185
186    public static void dump(PrintWriter pw) {
187        synchronized (DozeLog.class) {
188            if (sMessages == null) return;
189            pw.println("  Doze log:");
190            final int start = (sPosition - sCount + SIZE) % SIZE;
191            for (int i = 0; i < sCount; i++) {
192                final int j = (start + i) % SIZE;
193                pw.print("    ");
194                pw.print(FORMAT.format(new Date(sTimes[j])));
195                pw.print(' ');
196                pw.println(sMessages[j]);
197            }
198            pw.print("  Doze summary stats (for ");
199            TimeUtils.formatDuration(System.currentTimeMillis() - sSince, pw);
200            pw.println("):");
201            sPickupPulseNearVibrationStats.dump(pw, "Pickup pulse (near vibration)");
202            sPickupPulseNotNearVibrationStats.dump(pw, "Pickup pulse (not near vibration)");
203            sNotificationPulseStats.dump(pw, "Notification pulse");
204            sScreenOnPulsingStats.dump(pw, "Screen on (pulsing)");
205            sScreenOnNotPulsingStats.dump(pw, "Screen on (not pulsing)");
206            sEmergencyCallStats.dump(pw, "Emergency call");
207            for (int i = 0; i < PULSE_REASONS; i++) {
208                final String reason = pulseReasonToString(i);
209                sProxStats[i][0].dump(pw, "Proximity near (" + reason + ")");
210                sProxStats[i][1].dump(pw, "Proximity far (" + reason + ")");
211            }
212        }
213    }
214
215    private static void log(String msg) {
216        synchronized (DozeLog.class) {
217            if (sMessages == null) return;
218            sTimes[sPosition] = System.currentTimeMillis();
219            sMessages[sPosition] = msg;
220            sPosition = (sPosition + 1) % SIZE;
221            sCount = Math.min(sCount + 1, SIZE);
222        }
223        if (DEBUG) Log.d(TAG, msg);
224    }
225
226    public static void tracePulseDropped(Context context, boolean pulsePending,
227            DozeMachine.State state, boolean blocked) {
228        if (!ENABLED) return;
229        init(context);
230        log("pulseDropped pulsePending=" + pulsePending + " state="
231                + state + " blocked=" + blocked);
232    }
233
234    public static void tracePulseCanceledByProx(Context context) {
235        if (!ENABLED) return;
236        init(context);
237        log("pulseCanceledByProx");
238    }
239
240    public static void setRegisterKeyguardCallback(boolean registerKeyguardCallback) {
241        if (!ENABLED) return;
242        synchronized (DozeLog.class) {
243            if (sRegisterKeyguardCallback != registerKeyguardCallback && sMessages != null) {
244                throw new IllegalStateException("Cannot change setRegisterKeyguardCallback "
245                        + "after init()");
246            }
247            sRegisterKeyguardCallback = registerKeyguardCallback;
248        }
249    }
250
251    private static class SummaryStats {
252        private int mCount;
253
254        public void append() {
255            mCount++;
256        }
257
258        public void dump(PrintWriter pw, String type) {
259            if (mCount == 0) return;
260            pw.print("    ");
261            pw.print(type);
262            pw.print(": n=");
263            pw.print(mCount);
264            pw.print(" (");
265            final double perHr = (double) mCount / (System.currentTimeMillis() - sSince)
266                    * 1000 * 60 * 60;
267            pw.print(perHr);
268            pw.print("/hr)");
269            pw.println();
270        }
271    }
272
273    private static final KeyguardUpdateMonitorCallback sKeyguardCallback =
274            new KeyguardUpdateMonitorCallback() {
275        @Override
276        public void onEmergencyCallAction() {
277            traceEmergencyCall();
278        }
279
280        @Override
281        public void onKeyguardBouncerChanged(boolean bouncer) {
282            traceKeyguardBouncerChanged(bouncer);
283        }
284
285        @Override
286        public void onStartedWakingUp() {
287            traceScreenOn();
288        }
289
290        @Override
291        public void onFinishedGoingToSleep(int why) {
292            traceScreenOff(why);
293        }
294
295        @Override
296        public void onKeyguardVisibilityChanged(boolean showing) {
297            traceKeyguard(showing);
298        }
299    };
300}
301