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    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
37
38    private static final int PULSE_REASONS = 4;
39
40    public static final int PULSE_REASON_INTENT = 0;
41    public static final int PULSE_REASON_NOTIFICATION = 1;
42    public static final int PULSE_REASON_SENSOR_SIGMOTION = 2;
43    public static final int PULSE_REASON_SENSOR_PICKUP = 3;
44
45    private static long[] sTimes;
46    private static String[] sMessages;
47    private static int sPosition;
48    private static int sCount;
49    private static boolean sPulsing;
50
51    private static long sSince;
52    private static SummaryStats sPickupPulseNearVibrationStats;
53    private static SummaryStats sPickupPulseNotNearVibrationStats;
54    private static SummaryStats sNotificationPulseStats;
55    private static SummaryStats sScreenOnPulsingStats;
56    private static SummaryStats sScreenOnNotPulsingStats;
57    private static SummaryStats sEmergencyCallStats;
58    private static SummaryStats[][] sProxStats; // [reason][near/far]
59
60    public static void tracePickupPulse(boolean withinVibrationThreshold) {
61        if (!ENABLED) return;
62        log("pickupPulse withinVibrationThreshold=" + withinVibrationThreshold);
63        (withinVibrationThreshold ? sPickupPulseNearVibrationStats
64                : sPickupPulseNotNearVibrationStats).append();
65    }
66
67    public static void tracePulseStart(int reason) {
68        if (!ENABLED) return;
69        sPulsing = true;
70        log("pulseStart reason=" + pulseReasonToString(reason));
71    }
72
73    public static void tracePulseFinish() {
74        if (!ENABLED) return;
75        sPulsing = false;
76        log("pulseFinish");
77    }
78
79    public static void traceNotificationPulse(long instance) {
80        if (!ENABLED) return;
81        log("notificationPulse instance=" + instance);
82        sNotificationPulseStats.append();
83    }
84
85    private static void init(Context context) {
86        synchronized (DozeLog.class) {
87            if (sMessages == null) {
88                sTimes = new long[SIZE];
89                sMessages = new String[SIZE];
90                sSince = System.currentTimeMillis();
91                sPickupPulseNearVibrationStats = new SummaryStats();
92                sPickupPulseNotNearVibrationStats = new SummaryStats();
93                sNotificationPulseStats = new SummaryStats();
94                sScreenOnPulsingStats = new SummaryStats();
95                sScreenOnNotPulsingStats = new SummaryStats();
96                sEmergencyCallStats = new SummaryStats();
97                sProxStats = new SummaryStats[PULSE_REASONS][2];
98                for (int i = 0; i < PULSE_REASONS; i++) {
99                    sProxStats[i][0] = new SummaryStats();
100                    sProxStats[i][1] = new SummaryStats();
101                }
102                log("init");
103                KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback);
104            }
105        }
106    }
107
108    public static void traceDozing(Context context, boolean dozing) {
109        if (!ENABLED) return;
110        sPulsing = false;
111        init(context);
112        log("dozing " + dozing);
113    }
114
115    public static void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
116            boolean screenOnFromTouch) {
117        if (!ENABLED) return;
118        log("fling expand=" + expand + " aboveThreshold=" + aboveThreshold + " thresholdNeeded="
119                + thresholdNeeded + " screenOnFromTouch=" + screenOnFromTouch);
120    }
121
122    public static void traceEmergencyCall() {
123        if (!ENABLED) return;
124        log("emergencyCall");
125        sEmergencyCallStats.append();
126    }
127
128    public static void traceKeyguardBouncerChanged(boolean showing) {
129        if (!ENABLED) return;
130        log("bouncer " + showing);
131    }
132
133    public static void traceScreenOn() {
134        if (!ENABLED) return;
135        log("screenOn pulsing=" + sPulsing);
136        (sPulsing ? sScreenOnPulsingStats : sScreenOnNotPulsingStats).append();
137        sPulsing = false;
138    }
139
140    public static void traceScreenOff(int why) {
141        if (!ENABLED) return;
142        log("screenOff why=" + why);
143    }
144
145    public static void traceKeyguard(boolean showing) {
146        if (!ENABLED) return;
147        log("keyguard " + showing);
148        if (!showing) {
149            sPulsing = false;
150        }
151    }
152
153    public static void traceProximityResult(Context context, boolean near, long millis,
154            int pulseReason) {
155        if (!ENABLED) return;
156        log("proximityResult reason=" + pulseReasonToString(pulseReason) + " near=" + near
157                + " millis=" + millis);
158        init(context);
159        sProxStats[pulseReason][near ? 0 : 1].append();
160    }
161
162    public static String pulseReasonToString(int pulseReason) {
163        switch (pulseReason) {
164            case PULSE_REASON_INTENT: return "intent";
165            case PULSE_REASON_NOTIFICATION: return "notification";
166            case PULSE_REASON_SENSOR_SIGMOTION: return "sigmotion";
167            case PULSE_REASON_SENSOR_PICKUP: return "pickup";
168            default: throw new IllegalArgumentException("bad reason: " + pulseReason);
169        }
170    }
171
172    public static void dump(PrintWriter pw) {
173        synchronized (DozeLog.class) {
174            if (sMessages == null) return;
175            pw.println("  Doze log:");
176            final int start = (sPosition - sCount + SIZE) % SIZE;
177            for (int i = 0; i < sCount; i++) {
178                final int j = (start + i) % SIZE;
179                pw.print("    ");
180                pw.print(FORMAT.format(new Date(sTimes[j])));
181                pw.print(' ');
182                pw.println(sMessages[j]);
183            }
184            pw.print("  Doze summary stats (for ");
185            TimeUtils.formatDuration(System.currentTimeMillis() - sSince, pw);
186            pw.println("):");
187            sPickupPulseNearVibrationStats.dump(pw, "Pickup pulse (near vibration)");
188            sPickupPulseNotNearVibrationStats.dump(pw, "Pickup pulse (not near vibration)");
189            sNotificationPulseStats.dump(pw, "Notification pulse");
190            sScreenOnPulsingStats.dump(pw, "Screen on (pulsing)");
191            sScreenOnNotPulsingStats.dump(pw, "Screen on (not pulsing)");
192            sEmergencyCallStats.dump(pw, "Emergency call");
193            for (int i = 0; i < PULSE_REASONS; i++) {
194                final String reason = pulseReasonToString(i);
195                sProxStats[i][0].dump(pw, "Proximity near (" + reason + ")");
196                sProxStats[i][1].dump(pw, "Proximity far (" + reason + ")");
197            }
198        }
199    }
200
201    private static void log(String msg) {
202        synchronized (DozeLog.class) {
203            if (sMessages == null) return;
204            sTimes[sPosition] = System.currentTimeMillis();
205            sMessages[sPosition] = msg;
206            sPosition = (sPosition + 1) % SIZE;
207            sCount = Math.min(sCount + 1, SIZE);
208        }
209        if (DEBUG) Log.d(TAG, msg);
210    }
211
212    private static class SummaryStats {
213        private int mCount;
214
215        public void append() {
216            mCount++;
217        }
218
219        public void dump(PrintWriter pw, String type) {
220            if (mCount == 0) return;
221            pw.print("    ");
222            pw.print(type);
223            pw.print(": n=");
224            pw.print(mCount);
225            pw.print(" (");
226            final double perHr = (double) mCount / (System.currentTimeMillis() - sSince)
227                    * 1000 * 60 * 60;
228            pw.print(perHr);
229            pw.print("/hr)");
230            pw.println();
231        }
232    }
233
234    private static final KeyguardUpdateMonitorCallback sKeyguardCallback =
235            new KeyguardUpdateMonitorCallback() {
236        @Override
237        public void onEmergencyCallAction() {
238            traceEmergencyCall();
239        }
240
241        @Override
242        public void onKeyguardBouncerChanged(boolean bouncer) {
243            traceKeyguardBouncerChanged(bouncer);
244        }
245
246        @Override
247        public void onStartedWakingUp() {
248            traceScreenOn();
249        }
250
251        @Override
252        public void onFinishedGoingToSleep(int why) {
253            traceScreenOff(why);
254        }
255
256        @Override
257        public void onKeyguardVisibilityChanged(boolean showing) {
258            traceKeyguard(showing);
259        }
260    };
261}
262