• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.systemui.doze;
18 
19 import static android.os.PowerManager.WAKE_REASON_BIOMETRIC;
20 import static android.os.PowerManager.WAKE_REASON_GESTURE;
21 import static android.os.PowerManager.WAKE_REASON_LIFT;
22 import static android.os.PowerManager.WAKE_REASON_PLUGGED_IN;
23 import static android.os.PowerManager.WAKE_REASON_TAP;
24 
25 import android.annotation.IntDef;
26 import android.os.PowerManager;
27 import android.util.TimeUtils;
28 
29 import androidx.annotation.NonNull;
30 
31 import com.android.keyguard.KeyguardUpdateMonitor;
32 import com.android.keyguard.KeyguardUpdateMonitorCallback;
33 import com.android.systemui.Dumpable;
34 import com.android.systemui.dagger.SysUISingleton;
35 import com.android.systemui.dump.DumpManager;
36 import com.android.systemui.statusbar.policy.DevicePostureController;
37 
38 import com.google.errorprone.annotations.CompileTimeConstant;
39 
40 import java.io.PrintWriter;
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 
44 import javax.inject.Inject;
45 
46 /**
47  * Logs doze events for debugging and triaging purposes. Logs are dumped in bugreports or on demand:
48  *      adb shell dumpsys activity service com.android.systemui/.SystemUIService \
49  *      dependency DumpController DozeLog,DozeStats
50  */
51 @SysUISingleton
52 public class DozeLog implements Dumpable {
53     private final DozeLogger mLogger;
54 
55     private boolean mPulsing;
56     private long mSince;
57     private SummaryStats mPickupPulseNearVibrationStats;
58     private SummaryStats mPickupPulseNotNearVibrationStats;
59     private SummaryStats mNotificationPulseStats;
60     private SummaryStats mScreenOnPulsingStats;
61     private SummaryStats mScreenOnNotPulsingStats;
62     private SummaryStats mEmergencyCallStats;
63     private SummaryStats[][] mProxStats; // [reason][near/far]
64 
65     @Inject
DozeLog( KeyguardUpdateMonitor keyguardUpdateMonitor, DumpManager dumpManager, DozeLogger logger)66     public DozeLog(
67             KeyguardUpdateMonitor keyguardUpdateMonitor,
68             DumpManager dumpManager,
69             DozeLogger logger) {
70         mLogger = logger;
71         mSince = System.currentTimeMillis();
72         mPickupPulseNearVibrationStats = new SummaryStats();
73         mPickupPulseNotNearVibrationStats = new SummaryStats();
74         mNotificationPulseStats = new SummaryStats();
75         mScreenOnPulsingStats = new SummaryStats();
76         mScreenOnNotPulsingStats = new SummaryStats();
77         mEmergencyCallStats = new SummaryStats();
78         mProxStats = new SummaryStats[TOTAL_REASONS][2];
79         for (int i = 0; i < TOTAL_REASONS; i++) {
80             mProxStats[i][0] = new SummaryStats();
81             mProxStats[i][1] = new SummaryStats();
82         }
83 
84         if (keyguardUpdateMonitor != null) {
85             keyguardUpdateMonitor.registerCallback(mKeyguardCallback);
86         }
87 
88         dumpManager.registerDumpable("DumpStats", this);
89     }
90 
91     /**
92      * Log debug message to LogBuffer.
93      */
d(@ompileTimeConstant String msg)94     public void d(@CompileTimeConstant String msg) {
95         mLogger.log(msg);
96     }
97 
98     /**
99      * Appends pickup wakeup event to the logs
100      */
tracePickupWakeUp(boolean withinVibrationThreshold)101     public void tracePickupWakeUp(boolean withinVibrationThreshold) {
102         mLogger.logPickupWakeup(withinVibrationThreshold);
103         (withinVibrationThreshold ? mPickupPulseNearVibrationStats
104                 : mPickupPulseNotNearVibrationStats).append();
105     }
106 
traceSetIgnoreTouchWhilePulsing(boolean ignoreTouch)107     public void traceSetIgnoreTouchWhilePulsing(boolean ignoreTouch) {
108         mLogger.logSetIgnoreTouchWhilePulsing(ignoreTouch);
109     }
110 
111     /**
112      * Appends pulse started event to the logs.
113      * @param reason why the pulse started
114      */
tracePulseStart(@eason int reason)115     public void tracePulseStart(@Reason int reason) {
116         mLogger.logPulseStart(reason);
117         mPulsing = true;
118     }
119 
120     /**
121      * Appends pulse finished event to the logs
122      */
tracePulseFinish()123     public void tracePulseFinish() {
124         mLogger.logPulseFinish();
125         mPulsing = false;
126     }
127 
128     /**
129      * Appends pulse event to the logs
130      */
traceNotificationPulse()131     public void traceNotificationPulse() {
132         mLogger.logNotificationPulse();
133         mNotificationPulseStats.append();
134     }
135 
136     /**
137      * Appends dozing event to the logs. Logs current dozing state when entering/exiting AOD.
138      * @param dozing true if dozing, else false
139      */
traceDozing(boolean dozing)140     public void traceDozing(boolean dozing) {
141         mLogger.logDozing(dozing);
142         mPulsing = false;
143     }
144 
145     /**
146      * Appends dozing event to the logs when dozing has changed in AOD.
147      * @param dozing true if we're now dozing, else false
148      */
traceDozingChanged(boolean dozing)149     public void traceDozingChanged(boolean dozing) {
150         mLogger.logDozingChanged(dozing);
151     }
152 
153     /**
154      * Appends fling event to the logs
155      */
traceFling(boolean expand, boolean aboveThreshold, boolean screenOnFromTouch)156     public void traceFling(boolean expand, boolean aboveThreshold,
157             boolean screenOnFromTouch) {
158         mLogger.logFling(expand, aboveThreshold, screenOnFromTouch);
159     }
160 
161     /**
162      * Appends emergency call event to the logs
163      */
traceEmergencyCall()164     public void traceEmergencyCall() {
165         mLogger.logEmergencyCall();
166         mEmergencyCallStats.append();
167     }
168 
169     /**
170      * Appends keyguard bouncer changed event to the logs
171      * @param showing true if the keyguard bouncer is showing, else false
172      */
traceKeyguardBouncerChanged(boolean showing)173     public void traceKeyguardBouncerChanged(boolean showing) {
174         mLogger.logKeyguardBouncerChanged(showing);
175     }
176 
177     /**
178      * Appends screen-on event to the logs
179      */
traceScreenOn()180     public void traceScreenOn() {
181         mLogger.logScreenOn(mPulsing);
182         (mPulsing ? mScreenOnPulsingStats : mScreenOnNotPulsingStats).append();
183         mPulsing = false;
184     }
185 
186     /**
187      * Appends screen-off event to the logs
188      * @param why reason the screen is off
189      */
traceScreenOff(int why)190     public void traceScreenOff(int why) {
191         mLogger.logScreenOff(why);
192     }
193 
194     /**
195      * Appends missed tick event to the logs
196      * @param delay of the missed tick
197      */
traceMissedTick(String delay)198     public void traceMissedTick(String delay) {
199         mLogger.logMissedTick(delay);
200     }
201 
202     /**
203      * Appends time tick scheduled event to the logs
204      * @param when time tick scheduled at
205      * @param triggerAt time tick trigger at
206      */
traceTimeTickScheduled(long when, long triggerAt)207     public void traceTimeTickScheduled(long when, long triggerAt) {
208         mLogger.logTimeTickScheduled(when, triggerAt);
209     }
210 
211     /**
212      * Logs cancelation requests for time ticks
213      * @param isPending is an unschedule request pending?
214      * @param isTimeTickScheduled is a time tick request scheduled
215      */
tracePendingUnscheduleTimeTick(boolean isPending, boolean isTimeTickScheduled)216     public void tracePendingUnscheduleTimeTick(boolean isPending, boolean isTimeTickScheduled) {
217         mLogger.logPendingUnscheduleTimeTick(isPending, isTimeTickScheduled);
218     }
219 
220     /**
221      * Appends keyguard visibility change event to the logs
222      * @param showing whether the keyguard is now showing
223      */
traceKeyguard(boolean showing)224     public void traceKeyguard(boolean showing) {
225         mLogger.logKeyguardVisibilityChange(showing);
226         if (!showing) mPulsing = false;
227     }
228 
229     /**
230      * Appends doze state changed event to the logs
231      * @param state new DozeMachine state
232      */
traceState(DozeMachine.State state)233     public void traceState(DozeMachine.State state) {
234         mLogger.logDozeStateChanged(state);
235     }
236 
237     /**
238      * Appends doze state changed sent to all DozeMachine parts event to the logs
239      * @param state new DozeMachine state
240      */
traceDozeStateSendComplete(DozeMachine.State state)241     public void traceDozeStateSendComplete(DozeMachine.State state) {
242         mLogger.logStateChangedSent(state);
243     }
244 
245     /**
246      * Appends display state delayed by UDFPS event to the logs
247      * @param delayedDisplayState the display screen state that was delayed
248      */
traceDisplayStateDelayedByUdfps(int delayedDisplayState)249     public void traceDisplayStateDelayedByUdfps(int delayedDisplayState) {
250         mLogger.logDisplayStateDelayedByUdfps(delayedDisplayState);
251     }
252 
253     /**
254      * Appends display state changed event to the logs
255      * @param displayState new DozeMachine state
256      * @param afterRequest whether the request has successfully been sent else false for it's
257      *                        about to be requested
258      */
traceDisplayState(int displayState, boolean afterRequest)259     public void traceDisplayState(int displayState, boolean afterRequest) {
260         mLogger.logDisplayStateChanged(displayState, afterRequest);
261     }
262 
263     /**
264      * Appends wake-display event to the logs.
265      * @param wake if we're waking up or sleeping.
266      */
traceWakeDisplay(boolean wake, @Reason int reason)267     public void traceWakeDisplay(boolean wake, @Reason int reason) {
268         mLogger.logWakeDisplay(wake, reason);
269     }
270 
271     /**
272      * Appends proximity result event to the logs
273      * @param near true if near, else false
274      * @param reason why proximity result was triggered
275      */
traceProximityResult(boolean near, long millis, @Reason int reason)276     public void traceProximityResult(boolean near, long millis, @Reason int reason) {
277         mLogger.logProximityResult(near, millis, reason);
278         mProxStats[reason][near ? 0 : 1].append();
279     }
280 
281     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)282     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
283         synchronized (DozeLog.class) {
284             pw.print("  Doze summary stats (for ");
285             TimeUtils.formatDuration(System.currentTimeMillis() - mSince, pw);
286             pw.println("):");
287             mPickupPulseNearVibrationStats.dump(pw, "Pickup pulse (near vibration)");
288             mPickupPulseNotNearVibrationStats.dump(pw, "Pickup pulse (not near vibration)");
289             mNotificationPulseStats.dump(pw, "Notification pulse");
290             mScreenOnPulsingStats.dump(pw, "Screen on (pulsing)");
291             mScreenOnNotPulsingStats.dump(pw, "Screen on (not pulsing)");
292             mEmergencyCallStats.dump(pw, "Emergency call");
293             for (int i = 0; i < TOTAL_REASONS; i++) {
294                 final String reason = reasonToString(i);
295                 mProxStats[i][0].dump(pw, "Proximity near (" + reason + ")");
296                 mProxStats[i][1].dump(pw, "Proximity far (" + reason + ")");
297             }
298         }
299     }
300 
301     /**
302      * Appends doze updates due to a posture change.
303      */
tracePostureChanged( @evicePostureController.DevicePostureInt int posture, String partUpdated )304     public void tracePostureChanged(
305             @DevicePostureController.DevicePostureInt int posture,
306             String partUpdated
307     ) {
308         mLogger.logPostureChanged(posture, partUpdated);
309     }
310 
311     /**
312      * Appends pulse dropped event to logs
313      */
tracePulseDropped(String from, DozeMachine.State state)314     public void tracePulseDropped(String from, DozeMachine.State state) {
315         mLogger.logPulseDropped(from, state);
316     }
317 
318     /**
319      * Appends sensor event dropped event to logs
320      */
traceSensorEventDropped(@eason int pulseReason, String reason)321     public void traceSensorEventDropped(@Reason int pulseReason, String reason) {
322         mLogger.logSensorEventDropped(pulseReason, reason);
323     }
324 
325     /**
326      * Appends pulsing event to logs.
327      */
tracePulseEvent(String pulseEvent, boolean dozing, int pulseReason)328     public void tracePulseEvent(String pulseEvent, boolean dozing, int pulseReason) {
329         mLogger.logPulseEvent(pulseEvent, dozing, DozeLog.reasonToString(pulseReason));
330     }
331 
332     /**
333      * Appends pulse dropped event to logs
334      * @param reason why the pulse was dropped
335      */
tracePulseDropped(String reason)336     public void tracePulseDropped(String reason) {
337         mLogger.logPulseDropped(reason);
338     }
339 
340     /**
341      * Appends pulse touch displayed by prox sensor event to logs
342      * @param disabled
343      */
tracePulseTouchDisabledByProx(boolean disabled)344     public void tracePulseTouchDisabledByProx(boolean disabled) {
345         mLogger.logPulseTouchDisabledByProx(disabled);
346     }
347 
348     /**
349      * Appends sensor triggered event to logs
350      * @param reason why the sensor was triggered
351      */
traceSensor(@eason int reason)352     public void traceSensor(@Reason int reason) {
353         mLogger.logSensorTriggered(reason);
354     }
355 
356     /**
357      * Appends the doze state that was suppressed to the doze event log
358      * @param suppressedState The {@link DozeMachine.State} that was suppressed
359      * @param reason what suppressed always on
360      */
traceAlwaysOnSuppressed(DozeMachine.State suppressedState, String reason)361     public void traceAlwaysOnSuppressed(DozeMachine.State suppressedState, String reason) {
362         mLogger.logAlwaysOnSuppressed(suppressedState, reason);
363     }
364 
365     /**
366      * Appends reason why doze immediately ended.
367      */
traceImmediatelyEndDoze(String reason)368     public void traceImmediatelyEndDoze(String reason) {
369         mLogger.logImmediatelyEndDoze(reason);
370     }
371 
372     /**
373      * Logs the car mode started event.
374      */
traceCarModeStarted()375     public void traceCarModeStarted() {
376         mLogger.logCarModeStarted();
377     }
378 
379     /**
380      * Logs the car mode ended event.
381      */
traceCarModeEnded()382     public void traceCarModeEnded() {
383         mLogger.logCarModeEnded();
384     }
385 
386     /**
387      * Appends power save changes that may cause a new doze state
388      * @param powerSaveActive true if power saving is active
389      * @param nextState the state that we'll transition to
390      */
tracePowerSaveChanged(boolean powerSaveActive, DozeMachine.State nextState)391     public void tracePowerSaveChanged(boolean powerSaveActive, DozeMachine.State nextState) {
392         mLogger.logPowerSaveChanged(powerSaveActive, nextState);
393     }
394 
395     /**
396      * Appends an event on AOD suppression change
397      * @param suppressed true if AOD is being suppressed
398      * @param nextState the state that we'll transition to
399      */
traceAlwaysOnSuppressedChange(boolean suppressed, DozeMachine.State nextState)400     public void traceAlwaysOnSuppressedChange(boolean suppressed, DozeMachine.State nextState) {
401         mLogger.logAlwaysOnSuppressedChange(suppressed, nextState);
402     }
403 
404     /**
405      * Appends new AOD screen brightness to logs
406      * @param brightness display brightness setting between 1 and 255
407      * @param afterRequest whether the request has successfully been sent else false for it's
408      *                        about to be requested
409      */
traceDozeScreenBrightness(int brightness, boolean afterRequest)410     public void traceDozeScreenBrightness(int brightness, boolean afterRequest) {
411         mLogger.logDozeScreenBrightness(brightness, afterRequest);
412     }
413 
414     /**
415      * Appends new AOD screen brightness to logs
416      * @param brightness display brightness setting between {@link PowerManager#BRIGHTNESS_MIN} and
417      *                   {@link PowerManager#BRIGHTNESS_MAX}
418      * @param afterRequest whether the request has successfully been sent else false for it's
419      *                        about to be requested
420      */
traceDozeScreenBrightnessFloat(float brightness, boolean afterRequest)421     public void traceDozeScreenBrightnessFloat(float brightness, boolean afterRequest) {
422         mLogger.logDozeScreenBrightnessFloat(brightness, afterRequest);
423     }
424 
425     /**
426     * Appends new AOD dimming scrim opacity to logs
427     * @param scrimOpacity
428      */
traceSetAodDimmingScrim(float scrimOpacity)429     public void traceSetAodDimmingScrim(float scrimOpacity) {
430         mLogger.logSetAodDimmingScrim((long) scrimOpacity);
431     }
432 
433     /**
434      * Appends sensor attempted to register and whether it was a successful registration.
435      */
traceSensorRegisterAttempt(String sensorName, boolean successfulRegistration)436     public void traceSensorRegisterAttempt(String sensorName, boolean successfulRegistration) {
437         mLogger.logSensorRegisterAttempt(sensorName, successfulRegistration);
438     }
439 
440     /**
441      * Appends sensor attempted to unregister and whether it was successfully unregistered.
442      */
traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered)443     public void traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered) {
444         mLogger.logSensorUnregisterAttempt(sensorInfo, successfullyUnregistered);
445     }
446 
447     /**
448      * Appends sensor attempted to unregister and whether it was successfully unregistered
449      * with a reason the sensor is being unregistered.
450      */
traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered, String reason)451     public void traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered,
452             String reason) {
453         mLogger.logSensorUnregisterAttempt(sensorInfo, successfullyUnregistered, reason);
454     }
455 
456     /**
457      * Appends the event of skipping a sensor registration since it's already registered.
458      */
traceSkipRegisterSensor(String sensorInfo)459     public void traceSkipRegisterSensor(String sensorInfo) {
460         mLogger.logSkipSensorRegistration(sensorInfo);
461     }
462 
463     /**
464      * Appends a plugin sensor was registered or unregistered event.
465      */
tracePluginSensorUpdate(boolean registered)466     public void tracePluginSensorUpdate(boolean registered) {
467         if (registered) {
468             mLogger.log("register plugin sensor");
469         } else {
470             mLogger.log("unregister plugin sensor");
471         }
472     }
473 
474     private class SummaryStats {
475         private int mCount;
476 
append()477         public void append() {
478             mCount++;
479         }
480 
dump(PrintWriter pw, String type)481         public void dump(PrintWriter pw, String type) {
482             if (mCount == 0) return;
483             pw.print("    ");
484             pw.print(type);
485             pw.print(": n=");
486             pw.print(mCount);
487             pw.print(" (");
488             final double perHr = (double) mCount / (System.currentTimeMillis() - mSince)
489                     * 1000 * 60 * 60;
490             pw.print(perHr);
491             pw.print("/hr)");
492             pw.println();
493         }
494     }
495 
496     private final KeyguardUpdateMonitorCallback mKeyguardCallback =
497             new KeyguardUpdateMonitorCallback() {
498         @Override
499         public void onEmergencyCallAction() {
500             traceEmergencyCall();
501         }
502 
503         @Override
504         public void onKeyguardBouncerFullyShowingChanged(boolean fullyShowing) {
505             traceKeyguardBouncerChanged(fullyShowing);
506         }
507 
508         @Override
509         public void onStartedWakingUp() {
510             traceScreenOn();
511         }
512 
513         @Override
514         public void onFinishedGoingToSleep(int why) {
515             traceScreenOff(why);
516         }
517 
518         @Override
519         public void onKeyguardVisibilityChanged(boolean visible) {
520             traceKeyguard(visible);
521         }
522     };
523 
524     /**
525      * Converts the reason (integer) to a user-readable string
526      */
reasonToString(@eason int pulseReason)527     public static String reasonToString(@Reason int pulseReason) {
528         switch (pulseReason) {
529             case PULSE_REASON_INTENT: return "intent";
530             case PULSE_REASON_NOTIFICATION: return "notification";
531             case PULSE_REASON_SENSOR_SIGMOTION: return "sigmotion";
532             case REASON_SENSOR_PICKUP: return "pickup";
533             case REASON_SENSOR_DOUBLE_TAP: return "doubletap";
534             case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress";
535             case PULSE_REASON_DOCKING: return "docking";
536             case PULSE_REASON_SENSOR_WAKE_REACH: return "reach-wakelockscreen";
537             case REASON_SENSOR_WAKE_UP_PRESENCE: return "presence-wakeup";
538             case REASON_SENSOR_TAP: return "tap";
539             case REASON_SENSOR_UDFPS_LONG_PRESS: return "udfps";
540             case REASON_SENSOR_QUICK_PICKUP: return "quickPickup";
541             case PULSE_REASON_FINGERPRINT_ACTIVATED: return "fingerprint-triggered";
542             default: throw new IllegalArgumentException("invalid reason: " + pulseReason);
543         }
544     }
545 
546     /**
547      * Converts {@link Reason} to {@link PowerManager.WakeReason}.
548      */
getPowerManagerWakeReason(@eason int wakeReason)549     public static @PowerManager.WakeReason int getPowerManagerWakeReason(@Reason int wakeReason) {
550         switch (wakeReason) {
551             case REASON_SENSOR_DOUBLE_TAP:
552             case REASON_SENSOR_TAP:
553                 return WAKE_REASON_TAP;
554             case REASON_SENSOR_PICKUP:
555                 return WAKE_REASON_LIFT;
556             case REASON_SENSOR_UDFPS_LONG_PRESS:
557                 return WAKE_REASON_BIOMETRIC;
558             case PULSE_REASON_DOCKING:
559                 return WAKE_REASON_PLUGGED_IN;
560             default:
561                 return WAKE_REASON_GESTURE;
562         }
563     }
564 
565     @Retention(RetentionPolicy.SOURCE)
566     @IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION,
567             PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP,
568             PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP_PRESENCE,
569             PULSE_REASON_SENSOR_WAKE_REACH, REASON_SENSOR_TAP,
570             REASON_SENSOR_UDFPS_LONG_PRESS, REASON_SENSOR_QUICK_PICKUP,
571             PULSE_REASON_FINGERPRINT_ACTIVATED
572     })
573     public @interface Reason {}
574     public static final int PULSE_REASON_NONE = -1;
575     public static final int PULSE_REASON_INTENT = 0;
576     public static final int PULSE_REASON_NOTIFICATION = 1;
577     public static final int PULSE_REASON_SENSOR_SIGMOTION = 2;
578     public static final int REASON_SENSOR_PICKUP = 3;
579     public static final int REASON_SENSOR_DOUBLE_TAP = 4;
580     public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5;
581     public static final int PULSE_REASON_DOCKING = 6;
582     public static final int REASON_SENSOR_WAKE_UP_PRESENCE = 7;
583     public static final int PULSE_REASON_SENSOR_WAKE_REACH = 8;
584     public static final int REASON_SENSOR_TAP = 9;
585     public static final int REASON_SENSOR_UDFPS_LONG_PRESS = 10;
586     public static final int REASON_SENSOR_QUICK_PICKUP = 11;
587     public static final int PULSE_REASON_FINGERPRINT_ACTIVATED = 12;
588 
589     public static final int TOTAL_REASONS = 13;
590 }
591