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.networkstack.metrics;
18 
19 import android.net.util.NetworkStackUtils;
20 import android.net.wifi.WifiInfo;
21 
22 import androidx.annotation.IntRange;
23 import androidx.annotation.NonNull;
24 import androidx.annotation.Nullable;
25 import androidx.annotation.VisibleForTesting;
26 
27 import com.android.internal.util.HexDump;
28 import com.android.server.connectivity.nano.CellularData;
29 import com.android.server.connectivity.nano.DataStallEventProto;
30 import com.android.server.connectivity.nano.DnsEvent;
31 import com.android.server.connectivity.nano.WifiData;
32 
33 import com.google.protobuf.nano.MessageNano;
34 
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.Objects;
39 
40 /**
41  * Class to record the stats of detection level information for data stall.
42  *
43  * @hide
44  */
45 public final class DataStallDetectionStats {
46     public static final int UNKNOWN_SIGNAL_STRENGTH = -1;
47     // Default value of TCP signals.
48     @VisibleForTesting
49     public static final int UNSPECIFIED_TCP_FAIL_RATE = -1;
50     @VisibleForTesting
51     public static final int UNSPECIFIED_TCP_PACKETS_COUNT = -1;
52     @VisibleForTesting
53     @NonNull
54     public final byte[] mCellularInfo;
55     @VisibleForTesting
56     @NonNull
57     public final byte[] mWifiInfo;
58     @NonNull
59     public final byte[] mDns;
60     @VisibleForTesting
61     public final int mEvaluationType;
62     @VisibleForTesting
63     public final int mNetworkType;
64     // The TCP packets fail rate percentage from the latest tcp polling. -1 means the TCP signal is
65     // not known or not supported on the SDK version of this device.
66     @VisibleForTesting @IntRange(from = -1, to = 100)
67     public final int mTcpFailRate;
68     // Number of packets sent since the last received packet.
69     @VisibleForTesting
70     public final int mTcpSentSinceLastRecv;
71 
DataStallDetectionStats(@ullable byte[] cell, @Nullable byte[] wifi, @NonNull int[] returnCode, @NonNull long[] dnsTime, int evalType, int netType, int failRate, int sentSinceLastRecv)72     public DataStallDetectionStats(@Nullable byte[] cell, @Nullable byte[] wifi,
73                 @NonNull int[] returnCode, @NonNull long[] dnsTime, int evalType, int netType,
74                 int failRate, int sentSinceLastRecv) {
75         mCellularInfo = emptyCellDataIfNull(cell);
76         mWifiInfo = emptyWifiInfoIfNull(wifi);
77 
78         DnsEvent dns = new DnsEvent();
79         dns.dnsReturnCode = returnCode;
80         dns.dnsTime = dnsTime;
81         mDns = MessageNano.toByteArray(dns);
82         mEvaluationType = evalType;
83         mNetworkType = netType;
84         mTcpFailRate = failRate;
85         mTcpSentSinceLastRecv = sentSinceLastRecv;
86     }
87 
88     /**
89      * Because metrics data must contain data for each field even if it's not supported or not
90      * available, generate a byte array representing an empty {@link CellularData} if the
91      * {@link CellularData} is unavailable.
92      *
93      * @param cell a byte array representing current {@link CellularData} of {@code this}
94      * @return a byte array of a {@link CellularData}.
95      */
96     @VisibleForTesting
emptyCellDataIfNull(@ullable byte[] cell)97     public static byte[] emptyCellDataIfNull(@Nullable byte[] cell) {
98         if (cell != null) return cell;
99 
100         CellularData data  = new CellularData();
101         data.ratType = DataStallEventProto.RADIO_TECHNOLOGY_UNKNOWN;
102         data.networkMccmnc = "";
103         data.simMccmnc = "";
104         data.signalStrength = UNKNOWN_SIGNAL_STRENGTH;
105         return MessageNano.toByteArray(data);
106     }
107 
108     /**
109      * Because metrics data must contain data for each field even if it's not supported or not
110      * available, generate a byte array representing an empty {@link WifiData} if the
111      * {@link WiFiData} is unavailable.
112      *
113      * @param wifi a byte array representing current {@link WiFiData} of {@code this}.
114      * @return a byte array of a {@link WiFiData}.
115      */
116     @VisibleForTesting
emptyWifiInfoIfNull(@ullable byte[] wifi)117     public static byte[] emptyWifiInfoIfNull(@Nullable byte[] wifi) {
118         if (wifi != null) return wifi;
119 
120         WifiData data = new WifiData();
121         data.wifiBand = DataStallEventProto.AP_BAND_UNKNOWN;
122         data.signalStrength = UNKNOWN_SIGNAL_STRENGTH;
123         return MessageNano.toByteArray(data);
124     }
125 
126     @Override
toString()127     public String toString() {
128         StringBuilder sb = new StringBuilder();
129         sb.append("type: ").append(mNetworkType)
130           .append(", evaluation type: ")
131           .append(mEvaluationType)
132           .append(", wifi info: ")
133           .append(HexDump.toHexString(mWifiInfo))
134           .append(", cell info: ")
135           .append(HexDump.toHexString(mCellularInfo))
136           .append(", dns: ")
137           .append(HexDump.toHexString(mDns))
138           .append(", tcp fail rate: ")
139           .append(mTcpFailRate)
140           .append(", tcp received: ")
141           .append(mTcpSentSinceLastRecv);
142         return sb.toString();
143     }
144 
145     @Override
equals(@ullable final Object o)146     public boolean equals(@Nullable final Object o) {
147         if (!(o instanceof DataStallDetectionStats)) return false;
148         final DataStallDetectionStats other = (DataStallDetectionStats) o;
149         return (mNetworkType == other.mNetworkType)
150             && (mEvaluationType == other.mEvaluationType)
151             && Arrays.equals(mWifiInfo, other.mWifiInfo)
152             && Arrays.equals(mCellularInfo, other.mCellularInfo)
153             && Arrays.equals(mDns, other.mDns)
154             && (mTcpFailRate == other.mTcpFailRate)
155             && (mTcpSentSinceLastRecv == other.mTcpSentSinceLastRecv);
156     }
157 
158     @Override
hashCode()159     public int hashCode() {
160         return Objects.hash(mNetworkType, mEvaluationType, mWifiInfo, mCellularInfo, mDns,
161                 mTcpFailRate, mTcpSentSinceLastRecv);
162     }
163 
164     /**
165      * Utility to create an instance of {@Link DataStallDetectionStats}
166      *
167      * @hide
168      */
169     public static class Builder {
170         @Nullable
171         private byte[] mCellularInfo;
172         @Nullable
173         private byte[] mWifiInfo;
174         @NonNull
175         private final List<Integer> mDnsReturnCode = new ArrayList<Integer>();
176         @NonNull
177         private final List<Long> mDnsTimeStamp = new ArrayList<Long>();
178         private int mEvaluationType;
179         private int mNetworkType;
180         private int mTcpFailRate = UNSPECIFIED_TCP_FAIL_RATE;
181         private int mTcpSentSinceLastRecv = UNSPECIFIED_TCP_PACKETS_COUNT;
182 
183         /**
184          * Add a dns event into Builder.
185          *
186          * @param code the return code of the dns event.
187          * @param timeMs the elapsedRealtime in ms that the the dns event was received from netd.
188          * @return {@code this} {@link Builder} instance.
189          */
addDnsEvent(int code, long timeMs)190         public Builder addDnsEvent(int code, long timeMs) {
191             mDnsReturnCode.add(code);
192             mDnsTimeStamp.add(timeMs);
193             return this;
194         }
195 
196         /**
197          * Set the data stall evaluation type into Builder.
198          *
199          * @param type the signal type causing a data stall to be suspected.
200          * @return {@code this} {@link Builder} instance.
201          */
setEvaluationType(int type)202         public Builder setEvaluationType(int type) {
203             mEvaluationType = type;
204             return this;
205         }
206 
207         /**
208          * Set the network type into Builder.
209          *
210          * @param type the network type of the logged network.
211          * @return {@code this} {@link Builder} instance.
212          */
setNetworkType(int type)213         public Builder setNetworkType(int type) {
214             mNetworkType = type;
215             return this;
216         }
217 
218         /**
219          * Set the TCP packet fail rate into Builder. The data is included since android R.
220          *
221          * @param rate the TCP packet fail rate of the logged network. The default value is
222          *               {@code UNSPECIFIED_TCP_FAIL_RATE}, which means the TCP signal is not known
223          *               or not supported on the SDK version of this device.
224          * @return {@code this} {@link Builder} instance.
225          */
setTcpFailRate(@ntRangefrom = -1, to = 100) int rate)226         public Builder setTcpFailRate(@IntRange(from = -1, to = 100) int rate) {
227             mTcpFailRate = rate;
228             return this;
229         }
230 
231         /**
232          * Set the number of TCP packets sent since the last received packet into Builder. The data
233          * starts to be included since android R.
234          *
235          * @param count the number of packets sent since the last received packet of the logged
236          *              network. Keep it unset as default value or set to
237          *              {@code UNSPECIFIED_TCP_PACKETS_COUNT} if the tcp signal is unsupported with
238          *              current device android sdk version or the packets count is unknown.
239          * @return {@code this} {@link Builder} instance.
240          */
setTcpSentSinceLastRecv(int count)241         public Builder setTcpSentSinceLastRecv(int count) {
242             mTcpSentSinceLastRecv = count;
243             return this;
244         }
245 
246         /**
247          * Set the wifi data into Builder.
248          *
249          * @param info a {@link WifiInfo} of the connected wifi network.
250          * @return {@code this} {@link Builder} instance.
251          */
setWiFiData(@ullable final WifiInfo info)252         public Builder setWiFiData(@Nullable final WifiInfo info) {
253             WifiData data = new WifiData();
254             data.wifiBand = getWifiBand(info);
255             data.signalStrength = (info != null) ? info.getRssi() : UNKNOWN_SIGNAL_STRENGTH;
256             mWifiInfo = MessageNano.toByteArray(data);
257             return this;
258         }
259 
getWifiBand(@ullable final WifiInfo info)260         private static int getWifiBand(@Nullable final WifiInfo info) {
261             if (info == null) return DataStallEventProto.AP_BAND_UNKNOWN;
262 
263             int freq = info.getFrequency();
264             // Refer to ScanResult.is5GHz() and ScanResult.is24GHz().
265             if (freq > 4900 && freq < 5900) {
266                 return DataStallEventProto.AP_BAND_5GHZ;
267             } else if (freq > 2400 && freq < 2500) {
268                 return DataStallEventProto.AP_BAND_2GHZ;
269             } else {
270                 return DataStallEventProto.AP_BAND_UNKNOWN;
271             }
272         }
273 
274         /**
275          * Set the cellular data into Builder.
276          *
277          * @param radioType the radio technology of the logged cellular network.
278          * @param roaming a boolean indicates if logged cellular network is roaming or not.
279          * @param networkMccmnc the mccmnc of the camped network.
280          * @param simMccmnc the mccmnc of the sim.
281          * @return {@code this} {@link Builder} instance.
282          */
setCellData(int radioType, boolean roaming, @NonNull String networkMccmnc, @NonNull String simMccmnc, int ss)283         public Builder setCellData(int radioType, boolean roaming,
284                 @NonNull String networkMccmnc, @NonNull String simMccmnc, int ss) {
285             CellularData data  = new CellularData();
286             data.ratType = radioType;
287             data.isRoaming = roaming;
288             data.networkMccmnc = networkMccmnc;
289             data.simMccmnc = simMccmnc;
290             data.signalStrength = ss;
291             mCellularInfo = MessageNano.toByteArray(data);
292             return this;
293         }
294 
295         /**
296          * Create a new {@Link DataStallDetectionStats}.
297          */
build()298         public DataStallDetectionStats build() {
299             return new DataStallDetectionStats(mCellularInfo, mWifiInfo,
300                     NetworkStackUtils.convertToIntArray(mDnsReturnCode),
301                     NetworkStackUtils.convertToLongArray(mDnsTimeStamp),
302                     mEvaluationType, mNetworkType, mTcpFailRate, mTcpSentSinceLastRecv);
303         }
304     }
305 }
306