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