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