1 /*
2  * Copyright (C) 2019 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.internal.telephony;
18 
19 import android.content.Context;
20 import android.hardware.radio.V1_0.RadioError;
21 import android.provider.Settings;
22 import android.telephony.AnomalyReporter;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.telephony.Rlog;
26 
27 import java.util.HashMap;
28 import java.util.UUID;
29 
30 /**
31  * This class aims to detect radio bug based on wakelock timeout and system error.
32  *
33  * {@hide}
34  */
35 public class RadioBugDetector {
36     private static final String TAG = "RadioBugDetector";
37 
38     /** Radio error constants */
39     private static final int RADIO_BUG_NONE = 0x00;
40     private static final int RADIO_BUG_REPETITIVE_WAKELOCK_TIMEOUT_ERROR = 0x01;
41     @VisibleForTesting
42     protected static final int RADIO_BUG_REPETITIVE_SYSTEM_ERROR = 0x02;
43 
44     /**
45      * Default configuration values for radio bug detection.
46      * The value should be large enough to avoid false alarm. From past log analysis, 10 wakelock
47      * timeout took around 1 hour. 100 accumulated system_err is an estimation for abnormal radio.
48      */
49     private static final int DEFAULT_WAKELOCK_TIMEOUT_COUNT_THRESHOLD = 10;
50     private static final int DEFAULT_SYSTEM_ERROR_COUNT_THRESHOLD = 100;
51 
52     private Context mContext;
53     private int mContinuousWakelockTimoutCount = 0;
54     private int mRadioBugStatus = RADIO_BUG_NONE;
55     private int mSlotId;
56     private int mWakelockTimeoutThreshold = 0;
57     private int mSystemErrorThreshold = 0;
58 
59     private HashMap<Integer, Integer> mSysErrRecord = new HashMap<Integer, Integer>();
60 
61     /** Constructor */
RadioBugDetector(Context context, int slotId)62     public RadioBugDetector(Context context, int slotId) {
63         mContext = context;
64         mSlotId = slotId;
65         init();
66     }
67 
init()68     private void init() {
69         mWakelockTimeoutThreshold = Settings.Global.getInt(
70                 mContext.getContentResolver(),
71                 Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
72                 DEFAULT_WAKELOCK_TIMEOUT_COUNT_THRESHOLD);
73         mSystemErrorThreshold = Settings.Global.getInt(
74                 mContext.getContentResolver(),
75                 Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD,
76                 DEFAULT_SYSTEM_ERROR_COUNT_THRESHOLD);
77     }
78 
79     /**
80      * Detect radio bug and notify this issue once the threshold is reached.
81      *
82      * @param requestType The command type information we retrieved
83      * @param error       The error we received
84      */
detectRadioBug(int requestType, int error)85     public synchronized void detectRadioBug(int requestType, int error) {
86         /**
87          * When this function is executed, it means RIL is alive. So, reset WakelockTimeoutCount.
88          * Regarding SYSTEM_ERR, although RIL is alive, the connection with modem may be broken.
89          * For this error, accumulate the count and check if broadcast should be sent or not.
90          * For normal response or other error, reset the count of SYSTEM_ERR of the specific
91          * request type and mRadioBugStatus if necessary
92          */
93         mContinuousWakelockTimoutCount = 0;
94         if (error == RadioError.SYSTEM_ERR) {
95             int errorCount = mSysErrRecord.getOrDefault(requestType, 0);
96             errorCount++;
97             mSysErrRecord.put(requestType, errorCount);
98             broadcastBug(true);
99         } else {
100             // Reset system error count if we get non-system error response.
101             mSysErrRecord.remove(requestType);
102             if (!isFrequentSystemError()) {
103                 mRadioBugStatus = RADIO_BUG_NONE;
104             }
105         }
106     }
107 
108     /**
109      * When wakelock timeout is detected, accumulate its count and check if broadcast should be
110      * sent or not.
111      */
processWakelockTimeout()112     public void processWakelockTimeout() {
113         mContinuousWakelockTimoutCount++;
114         broadcastBug(false);
115     }
116 
broadcastBug(boolean isSystemError)117     private synchronized void broadcastBug(boolean isSystemError) {
118         if (isSystemError) {
119             if (!isFrequentSystemError()) {
120                 return;
121             }
122         } else {
123             if (mContinuousWakelockTimoutCount < mWakelockTimeoutThreshold) {
124                 return;
125             }
126         }
127 
128         // Notify that the RIL is stuck if SYSTEM_ERR or WAKE_LOCK_TIMEOUT returned by vendor
129         // RIL is more than the threshold times.
130         if (mRadioBugStatus == RADIO_BUG_NONE) {
131             mRadioBugStatus = isSystemError ? RADIO_BUG_REPETITIVE_SYSTEM_ERROR :
132                     RADIO_BUG_REPETITIVE_WAKELOCK_TIMEOUT_ERROR;
133             String message = "Repeated radio error " + mRadioBugStatus + " on slot " + mSlotId;
134             Rlog.d(TAG, message);
135             // Using fixed UUID to avoid duplicate bugreport notification
136             AnomalyReporter.reportAnomaly(
137                     UUID.fromString("d264ead0-3f05-11ea-b77f-2e728ce88125"),
138                     message);
139         }
140     }
141 
isFrequentSystemError()142     private boolean isFrequentSystemError() {
143         int countForError = 0;
144         boolean error = false;
145         for (int count : mSysErrRecord.values()) {
146             countForError += count;
147             if (countForError >= mSystemErrorThreshold) {
148                 error = true;
149                 break;
150             }
151         }
152         return error;
153     }
154 
155     @VisibleForTesting
getRadioBugStatus()156     public int getRadioBugStatus() {
157         return mRadioBugStatus;
158     }
159 
160     @VisibleForTesting
getWakelockTimeoutThreshold()161     public int getWakelockTimeoutThreshold() {
162         return mWakelockTimeoutThreshold;
163     }
164 
165     @VisibleForTesting
getSystemErrorThreshold()166     public int getSystemErrorThreshold() {
167         return mSystemErrorThreshold;
168     }
169 
170     @VisibleForTesting
getWakelockTimoutCount()171     public int getWakelockTimoutCount() {
172         return mContinuousWakelockTimoutCount;
173     }
174 
175 }
176 
177