summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src/com/android/systemui/volume/Events.java
blob: 369552fc814dd8c4fef5a96375a7ecf2d7d0ea50 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.volume;

import android.media.AudioManager;
import android.media.AudioSystem;
import android.provider.Settings.Global;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.plugins.VolumeDialogController.State;

import java.util.Arrays;

/**
 *  Interesting events related to the volume.
 */
public class Events {
    private static final String TAG = Util.logTag(Events.class);

    public static final int EVENT_SHOW_DIALOG = 0;  // (reason|int) (keyguard|bool)
    public static final int EVENT_DISMISS_DIALOG = 1; // (reason|int)
    public static final int EVENT_ACTIVE_STREAM_CHANGED = 2; // (stream|int)
    public static final int EVENT_EXPAND = 3; // (expand|bool)
    public static final int EVENT_KEY = 4; // (stream|int) (lastAudibleStreamVolume)
    public static final int EVENT_COLLECTION_STARTED = 5;
    public static final int EVENT_COLLECTION_STOPPED = 6;
    public static final int EVENT_ICON_CLICK = 7; // (stream|int) (icon_state|int)
    public static final int EVENT_SETTINGS_CLICK = 8;
    public static final int EVENT_TOUCH_LEVEL_CHANGED = 9; // (stream|int) (level|int)
    public static final int EVENT_LEVEL_CHANGED = 10; // (stream|int) (level|int)
    public static final int EVENT_INTERNAL_RINGER_MODE_CHANGED = 11; // (mode|int)
    public static final int EVENT_EXTERNAL_RINGER_MODE_CHANGED = 12; // (mode|int)
    public static final int EVENT_ZEN_MODE_CHANGED = 13; // (mode|int)
    public static final int EVENT_SUPPRESSOR_CHANGED = 14;  // (component|string) (name|string)
    public static final int EVENT_MUTE_CHANGED = 15;  // (stream|int) (muted|bool)
    public static final int EVENT_TOUCH_LEVEL_DONE = 16;  // (stream|int) (level|int)
    public static final int EVENT_ZEN_CONFIG_CHANGED = 17; // (allow/disallow|string)
    public static final int EVENT_RINGER_TOGGLE = 18; // (ringer_mode)
    public static final int EVENT_SHOW_USB_OVERHEAT_ALARM = 19; // (reason|int) (keyguard|bool)
    public static final int EVENT_DISMISS_USB_OVERHEAT_ALARM = 20; // (reason|int) (keyguard|bool)
    public static final int EVENT_ODI_CAPTIONS_CLICK = 21;
    public static final int EVENT_ODI_CAPTIONS_TOOLTIP_CLICK = 22;

    private static final String[] EVENT_TAGS = {
            "show_dialog",
            "dismiss_dialog",
            "active_stream_changed",
            "expand",
            "key",
            "collection_started",
            "collection_stopped",
            "icon_click",
            "settings_click",
            "touch_level_changed",
            "level_changed",
            "internal_ringer_mode_changed",
            "external_ringer_mode_changed",
            "zen_mode_changed",
            "suppressor_changed",
            "mute_changed",
            "touch_level_done",
            "zen_mode_config_changed",
            "ringer_toggle",
            "show_usb_overheat_alarm",
            "dismiss_usb_overheat_alarm",
            "odi_captions_click",
            "odi_captions_tooltip_click"
    };

    public static final int DISMISS_REASON_UNKNOWN = 0;
    public static final int DISMISS_REASON_TOUCH_OUTSIDE = 1;
    public static final int DISMISS_REASON_VOLUME_CONTROLLER = 2;
    public static final int DISMISS_REASON_TIMEOUT = 3;
    public static final int DISMISS_REASON_SCREEN_OFF = 4;
    public static final int DISMISS_REASON_SETTINGS_CLICKED = 5;
    public static final int DISMISS_REASON_DONE_CLICKED = 6;
    public static final int DISMISS_STREAM_GONE = 7;
    public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8;
    public static final int DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED = 9;
    public static final String[] DISMISS_REASONS = {
            "unknown",
            "touch_outside",
            "volume_controller",
            "timeout",
            "screen_off",
            "settings_clicked",
            "done_clicked",
            "a11y_stream_changed",
            "output_chooser",
            "usb_temperature_below_threshold"
    };

    public static final int SHOW_REASON_UNKNOWN = 0;
    public static final int SHOW_REASON_VOLUME_CHANGED = 1;
    public static final int SHOW_REASON_REMOTE_VOLUME_CHANGED = 2;
    public static final int SHOW_REASON_USB_OVERHEAD_ALARM_CHANGED = 3;
    public static final String[] SHOW_REASONS = {
        "unknown",
        "volume_changed",
        "remote_volume_changed",
        "usb_temperature_above_threshold"
    };

    public static final int ICON_STATE_UNKNOWN = 0;
    public static final int ICON_STATE_UNMUTE = 1;
    public static final int ICON_STATE_MUTE = 2;
    public static final int ICON_STATE_VIBRATE = 3;

    @VisibleForTesting
    public enum VolumeDialogOpenEvent implements UiEventLogger.UiEventEnum {
        //TODO zap the lock/unlock distinction
        INVALID(0),
        @UiEvent(doc = "The volume dialog was shown because the volume changed")
        VOLUME_DIALOG_SHOW_VOLUME_CHANGED(128),
        @UiEvent(doc = "The volume dialog was shown because the volume changed remotely")
        VOLUME_DIALOG_SHOW_REMOTE_VOLUME_CHANGED(129),
        @UiEvent(doc = "The volume dialog was shown because the usb high temperature alarm changed")
        VOLUME_DIALOG_SHOW_USB_TEMP_ALARM_CHANGED(130);

        private final int mId;
        VolumeDialogOpenEvent(int id) {
            mId = id;
        }
        public int getId() {
            return mId;
        }
        static VolumeDialogOpenEvent fromReasons(int reason) {
            switch (reason) {
                case SHOW_REASON_VOLUME_CHANGED:
                    return VOLUME_DIALOG_SHOW_VOLUME_CHANGED;
                case SHOW_REASON_REMOTE_VOLUME_CHANGED:
                    return VOLUME_DIALOG_SHOW_REMOTE_VOLUME_CHANGED;
                case SHOW_REASON_USB_OVERHEAD_ALARM_CHANGED:
                    return VOLUME_DIALOG_SHOW_USB_TEMP_ALARM_CHANGED;
            }
            return INVALID;
        }
    }

    @VisibleForTesting
    public enum VolumeDialogCloseEvent implements UiEventLogger.UiEventEnum {
        INVALID(0),
        @UiEvent(doc = "The volume dialog was dismissed because of a touch outside the dialog")
        VOLUME_DIALOG_DISMISS_TOUCH_OUTSIDE(134),
        @UiEvent(doc = "The system asked the volume dialog to close, e.g. for a navigation bar "
                 + "touch, or ActivityManager ACTION_CLOSE_SYSTEM_DIALOGS broadcast.")
        VOLUME_DIALOG_DISMISS_SYSTEM(135),
        @UiEvent(doc = "The volume dialog was dismissed because it timed out")
        VOLUME_DIALOG_DISMISS_TIMEOUT(136),
        @UiEvent(doc = "The volume dialog was dismissed because the screen turned off")
        VOLUME_DIALOG_DISMISS_SCREEN_OFF(137),
        @UiEvent(doc = "The volume dialog was dismissed because the settings icon was clicked")
        VOLUME_DIALOG_DISMISS_SETTINGS(138),
        // reserving 139 for DISMISS_REASON_DONE_CLICKED which is currently unused
        @UiEvent(doc = "The volume dialog was dismissed because the stream no longer exists")
        VOLUME_DIALOG_DISMISS_STREAM_GONE(140),
        // reserving 141 for DISMISS_REASON_OUTPUT_CHOOSER which is currently unused
        @UiEvent(doc = "The volume dialog was dismissed because the usb high temperature alarm "
                 + "changed")
        VOLUME_DIALOG_DISMISS_USB_TEMP_ALARM_CHANGED(142);

        private final int mId;
        VolumeDialogCloseEvent(int id) {
            mId = id;
        }
        public int getId() {
            return mId;
        }

        static VolumeDialogCloseEvent fromReason(int reason) {
            switch (reason) {
                case DISMISS_REASON_TOUCH_OUTSIDE:
                    return VOLUME_DIALOG_DISMISS_TOUCH_OUTSIDE;
                case DISMISS_REASON_VOLUME_CONTROLLER:
                    return VOLUME_DIALOG_DISMISS_SYSTEM;
                case DISMISS_REASON_TIMEOUT:
                    return VOLUME_DIALOG_DISMISS_TIMEOUT;
                case DISMISS_REASON_SCREEN_OFF:
                    return VOLUME_DIALOG_DISMISS_SCREEN_OFF;
                case DISMISS_REASON_SETTINGS_CLICKED:
                    return VOLUME_DIALOG_DISMISS_SETTINGS;
                case DISMISS_STREAM_GONE:
                    return VOLUME_DIALOG_DISMISS_STREAM_GONE;
                case DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED:
                    return VOLUME_DIALOG_DISMISS_USB_TEMP_ALARM_CHANGED;
            }
            return INVALID;
        }
    }

    @VisibleForTesting
    public enum VolumeDialogEvent implements UiEventLogger.UiEventEnum {
        INVALID(0),
        @UiEvent(doc = "The volume dialog settings icon was clicked")
        VOLUME_DIALOG_SETTINGS_CLICK(143),
        @UiEvent(doc = "The volume dialog details were expanded")
        VOLUME_DIALOG_EXPAND_DETAILS(144),
        @UiEvent(doc = "The volume dialog details were collapsed")
        VOLUME_DIALOG_COLLAPSE_DETAILS(145),
        @UiEvent(doc = "The active audio stream changed")
        VOLUME_DIALOG_ACTIVE_STREAM_CHANGED(146),
        @UiEvent(doc = "The audio stream was muted via icon")
        VOLUME_DIALOG_MUTE_STREAM(147),
        @UiEvent(doc = "The audio stream was unmuted via icon")
        VOLUME_DIALOG_UNMUTE_STREAM(148),
        @UiEvent(doc = "The audio stream was set to vibrate via icon")
        VOLUME_DIALOG_TO_VIBRATE_STREAM(149),
        @UiEvent(doc = "The audio stream was set to non-silent via slider")
        VOLUME_DIALOG_SLIDER(150),
        @UiEvent(doc = "The audio stream was set to silent via slider")
        VOLUME_DIALOG_SLIDER_TO_ZERO(151),
        @UiEvent(doc = "The audio volume was adjusted to silent via key")
        VOLUME_KEY_TO_ZERO(152),
        @UiEvent(doc = "The audio volume was adjusted to non-silent via key")
        VOLUME_KEY(153),
        @UiEvent(doc = "The ringer mode was toggled to silent")
        RINGER_MODE_SILENT(154),
        @UiEvent(doc = "The ringer mode was toggled to vibrate")
        RINGER_MODE_VIBRATE(155),
        @UiEvent(doc = "The ringer mode was toggled to normal")
        RINGER_MODE_NORMAL(334),
        @UiEvent(doc = "USB Overheat alarm was raised")
        USB_OVERHEAT_ALARM(160),
        @UiEvent(doc = "USB Overheat alarm was dismissed")
        USB_OVERHEAT_ALARM_DISMISSED(161);

        private final int mId;

        VolumeDialogEvent(int id) {
            mId = id;
        }

        public int getId() {
            return mId;
        }

        static VolumeDialogEvent fromIconState(int iconState) {
            switch (iconState) {
                case ICON_STATE_UNMUTE:
                    return VOLUME_DIALOG_UNMUTE_STREAM;
                case ICON_STATE_MUTE:
                    return VOLUME_DIALOG_MUTE_STREAM;
                case ICON_STATE_VIBRATE:
                    return VOLUME_DIALOG_TO_VIBRATE_STREAM;
                default:
                    return INVALID;
            }
        }

        static VolumeDialogEvent fromSliderLevel(int level) {
            return level == 0 ? VOLUME_DIALOG_SLIDER_TO_ZERO : VOLUME_DIALOG_SLIDER;
        }

        static VolumeDialogEvent fromKeyLevel(int level) {
            return level == 0 ? VOLUME_KEY_TO_ZERO : VOLUME_KEY;
        }

        static VolumeDialogEvent fromRingerMode(int ringerMode) {
            switch (ringerMode) {
                case AudioManager.RINGER_MODE_SILENT:
                    return RINGER_MODE_SILENT;
                case AudioManager.RINGER_MODE_VIBRATE:
                    return RINGER_MODE_VIBRATE;
                case AudioManager.RINGER_MODE_NORMAL:
                    return RINGER_MODE_NORMAL;
                default:
                    return INVALID;
            }
        }
    }

    @VisibleForTesting
    public enum ZenModeEvent implements UiEventLogger.UiEventEnum {
        INVALID(0),
        @UiEvent(doc = "Zen (do not disturb) mode was toggled to off")
        ZEN_MODE_OFF(335),
        @UiEvent(doc = "Zen (do not disturb) mode was toggled to important interruptions only")
        ZEN_MODE_IMPORTANT_ONLY(157),
        @UiEvent(doc = "Zen (do not disturb) mode was toggled to alarms only")
        ZEN_MODE_ALARMS_ONLY(158),
        @UiEvent(doc = "Zen (do not disturb) mode was toggled to block all interruptions")
        ZEN_MODE_NO_INTERRUPTIONS(159);

        private final int mId;
        ZenModeEvent(int id) {
            mId = id;
        }
        public int getId() {
            return mId;
        }

        static ZenModeEvent fromZenMode(int zenMode) {
            switch (zenMode) {
                case Global.ZEN_MODE_OFF: return ZEN_MODE_OFF;
                case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return ZEN_MODE_IMPORTANT_ONLY;
                case Global.ZEN_MODE_ALARMS: return ZEN_MODE_ALARMS_ONLY;
                case Global.ZEN_MODE_NO_INTERRUPTIONS: return ZEN_MODE_NO_INTERRUPTIONS;
                default: return INVALID;
            }
        }
    }

    public static Callback sCallback;
    @VisibleForTesting
    static MetricsLogger sLegacyLogger = new MetricsLogger();
    @VisibleForTesting
    static UiEventLogger sUiEventLogger = new UiEventLoggerImpl();

    /**
     * Logs an event to the system log, to sCallback if present, and to the logEvent destinations.
     * @param tag One of the EVENT_* codes above.
     * @param list Any additional event-specific arguments, documented above.
     */
    public static void writeEvent(int tag, Object... list) {
        final long time = System.currentTimeMillis();
        Log.i(TAG, logEvent(tag, list));
        if (sCallback != null) {
            sCallback.writeEvent(time, tag, list);
        }
    }

    /**
     * Logs an event to the event log and UiEvent (statsd) logging. Compare writeEvent, which
     * adds more log destinations.
     * @param tag One of the EVENT_* codes above.
     * @param list Any additional event-specific arguments, documented above.
     * @return String a readable description of the event.  Begins "writeEvent <tag_description>"
     * if the tag is valid.
     */
    public static String logEvent(int tag, Object... list) {
        if (tag >= EVENT_TAGS.length) {
            return "";
        }
        final StringBuilder sb = new StringBuilder("writeEvent ").append(EVENT_TAGS[tag]);
        // Handle events without extra data
        if (list == null || list.length == 0) {
            if (tag == EVENT_SETTINGS_CLICK) {
                sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_SETTINGS);
                sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_SETTINGS_CLICK);
            }
            return sb.toString();
        }
        // Handle events with extra data. We've established list[0] exists.
        sb.append(" ");
        switch (tag) {
            case EVENT_SHOW_DIALOG:
                sLegacyLogger.visible(MetricsEvent.VOLUME_DIALOG);
                if (list.length > 1) {
                    final Integer reason = (Integer) list[0];
                    final Boolean keyguard = (Boolean) list[1];
                    sLegacyLogger.histogram("volume_from_keyguard", keyguard ? 1 : 0);
                    sUiEventLogger.log(VolumeDialogOpenEvent.fromReasons(reason));
                    sb.append(SHOW_REASONS[reason]).append(" keyguard=").append(keyguard);
                }
                break;
            case EVENT_EXPAND: {
                final Boolean expand = (Boolean) list[0];
                sLegacyLogger.visibility(MetricsEvent.VOLUME_DIALOG_DETAILS, expand);
                sUiEventLogger.log(expand ? VolumeDialogEvent.VOLUME_DIALOG_EXPAND_DETAILS
                        : VolumeDialogEvent.VOLUME_DIALOG_COLLAPSE_DETAILS);
                sb.append(expand);
                break;
            }
            case EVENT_DISMISS_DIALOG: {
                sLegacyLogger.hidden(MetricsEvent.VOLUME_DIALOG);
                final Integer reason = (Integer) list[0];
                sUiEventLogger.log(VolumeDialogCloseEvent.fromReason(reason));
                sb.append(DISMISS_REASONS[reason]);
                break;
            }
            case EVENT_ACTIVE_STREAM_CHANGED: {
                final Integer stream = (Integer) list[0];
                sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_STREAM, stream);
                sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ACTIVE_STREAM_CHANGED);
                sb.append(AudioSystem.streamToString(stream));
                break;
            }
            case EVENT_ICON_CLICK:
                if (list.length > 1) {
                    final Integer stream = (Integer) list[0];
                    sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_ICON, stream);
                    final Integer iconState = (Integer) list[1];
                    sUiEventLogger.log(VolumeDialogEvent.fromIconState(iconState));
                    sb.append(AudioSystem.streamToString(stream)).append(' ')
                            .append(iconStateToString(iconState));
                }
                break;
            case EVENT_TOUCH_LEVEL_DONE: // (stream|int) (level|int)
                if (list.length > 1) {
                    final Integer level = (Integer) list[1];
                    sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_SLIDER, level);
                    sUiEventLogger.log(VolumeDialogEvent.fromSliderLevel(level));
                }
                // fall through
            case EVENT_TOUCH_LEVEL_CHANGED:
            case EVENT_LEVEL_CHANGED:
            case EVENT_MUTE_CHANGED:  // (stream|int) (level|int)
                if (list.length > 1) {
                    sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
                            .append(list[1]);
                }
                break;
            case EVENT_KEY: // (stream|int) (lastAudibleStreamVolume)
                if (list.length > 1) {
                    final Integer stream = (Integer) list[0];
                    sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_KEY, stream);
                    final Integer level = (Integer) list[1];
                    sUiEventLogger.log(VolumeDialogEvent.fromKeyLevel(level));
                    sb.append(AudioSystem.streamToString(stream)).append(' ').append(level);
                }
                break;
            case EVENT_RINGER_TOGGLE: {
                final Integer ringerMode = (Integer) list[0];
                sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_RINGER_TOGGLE, ringerMode);
                sUiEventLogger.log(VolumeDialogEvent.fromRingerMode(ringerMode));
                sb.append(ringerModeToString(ringerMode));
                break;
            }
            case EVENT_EXTERNAL_RINGER_MODE_CHANGED: {
                final Integer ringerMode = (Integer) list[0];
                sLegacyLogger.action(MetricsEvent.ACTION_RINGER_MODE, ringerMode);
            }
                // fall through
            case EVENT_INTERNAL_RINGER_MODE_CHANGED: {
                final Integer ringerMode = (Integer) list[0];
                sb.append(ringerModeToString(ringerMode));
                break;
            }
            case EVENT_ZEN_MODE_CHANGED: {
                final Integer zenMode = (Integer) list[0];
                sb.append(zenModeToString(zenMode));
                sUiEventLogger.log(ZenModeEvent.fromZenMode(zenMode));
                break;
            }
            case EVENT_SUPPRESSOR_CHANGED:  // (component|string) (name|string)
                if (list.length > 1) {
                    sb.append(list[0]).append(' ').append(list[1]);
                }
                break;
            case EVENT_SHOW_USB_OVERHEAT_ALARM:
                sLegacyLogger.visible(MetricsEvent.POWER_OVERHEAT_ALARM);
                sUiEventLogger.log(VolumeDialogEvent.USB_OVERHEAT_ALARM);
                if (list.length > 1) {
                    final Boolean keyguard = (Boolean) list[1];
                    sLegacyLogger.histogram("show_usb_overheat_alarm", keyguard ? 1 : 0);
                    final Integer reason = (Integer) list[0];
                    sb.append(SHOW_REASONS[reason]).append(" keyguard=").append(keyguard);
                }
                break;
            case EVENT_DISMISS_USB_OVERHEAT_ALARM:
                sLegacyLogger.hidden(MetricsEvent.POWER_OVERHEAT_ALARM);
                sUiEventLogger.log(VolumeDialogEvent.USB_OVERHEAT_ALARM_DISMISSED);
                if (list.length > 1) {
                    final Boolean keyguard = (Boolean) list[1];
                    sLegacyLogger.histogram("dismiss_usb_overheat_alarm", keyguard ? 1 : 0);
                    final Integer reason = (Integer) list[0];
                    sb.append(DISMISS_REASONS[reason])
                            .append(" keyguard=").append(keyguard);
                }
                break;
            default:
                sb.append(Arrays.asList(list));
                break;
        }
        return sb.toString();
    }

    public static void writeState(long time, State state) {
        if (sCallback != null) {
            sCallback.writeState(time, state);
        }
    }

    private static String iconStateToString(int iconState) {
        switch (iconState) {
            case ICON_STATE_UNMUTE: return "unmute";
            case ICON_STATE_MUTE: return "mute";
            case ICON_STATE_VIBRATE: return "vibrate";
            default: return "unknown_state_" + iconState;
        }
    }

    private static String ringerModeToString(int ringerMode) {
        switch (ringerMode) {
            case AudioManager.RINGER_MODE_SILENT: return "silent";
            case AudioManager.RINGER_MODE_VIBRATE: return "vibrate";
            case AudioManager.RINGER_MODE_NORMAL: return "normal";
            default: return "unknown";
        }
    }

    private static String zenModeToString(int zenMode) {
        switch (zenMode) {
            case Global.ZEN_MODE_OFF: return "off";
            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return "important_interruptions";
            case Global.ZEN_MODE_ALARMS: return "alarms";
            case Global.ZEN_MODE_NO_INTERRUPTIONS: return "no_interruptions";
            default: return "unknown";
        }
    }

    public interface Callback {
        void writeEvent(long time, int tag, Object[] list);
        void writeState(long time, State state);
    }

}