1 /*
2  * Copyright (C) 2022 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.tethering.metrics;
18 
19 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
20 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
21 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
22 import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
23 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
24 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
25 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
26 import static android.net.TetheringManager.TETHERING_ETHERNET;
27 import static android.net.TetheringManager.TETHERING_NCM;
28 import static android.net.TetheringManager.TETHERING_USB;
29 import static android.net.TetheringManager.TETHERING_WIFI;
30 import static android.net.TetheringManager.TETHERING_WIFI_P2P;
31 import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
32 import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR;
33 import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
34 import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
35 import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
36 import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
37 import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
38 import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
39 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
40 import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
41 import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
42 import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
43 import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
44 import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
45 import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
46 import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
47 
48 import android.annotation.Nullable;
49 import android.net.NetworkCapabilities;
50 import android.stats.connectivity.DownstreamType;
51 import android.stats.connectivity.ErrorCode;
52 import android.stats.connectivity.UpstreamType;
53 import android.stats.connectivity.UserType;
54 import android.util.Log;
55 import android.util.SparseArray;
56 
57 import androidx.annotation.NonNull;
58 import androidx.annotation.VisibleForTesting;
59 
60 import com.android.networkstack.tethering.UpstreamNetworkState;
61 
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 
65 /**
66  * Collection of utilities for tethering metrics.
67  *
68  * To see if the logs are properly sent to statsd, execute following commands
69  *
70  * $ adb shell cmd stats print-logs
71  * $ adb logcat | grep statsd OR $ adb logcat -b stats
72  *
73  * @hide
74  */
75 public class TetheringMetrics {
76     private static final String TAG = TetheringMetrics.class.getSimpleName();
77     private static final boolean DBG = false;
78     private static final String SETTINGS_PKG_NAME = "com.android.settings";
79     private static final String SYSTEMUI_PKG_NAME = "com.android.systemui";
80     private static final String GMS_PKG_NAME = "com.google.android.gms";
81     private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
82     private final SparseArray<Long> mDownstreamStartTime = new SparseArray<Long>();
83     private final ArrayList<RecordUpstreamEvent> mUpstreamEventList = new ArrayList<>();
84     private UpstreamType mCurrentUpstream = null;
85     private Long mCurrentUpStreamStartTime = 0L;
86 
87 
88     /**
89      * Return the current system time in milliseconds.
90      * @return the current system time in milliseconds.
91      */
timeNow()92     public long timeNow() {
93         return System.currentTimeMillis();
94     }
95 
96     private static class RecordUpstreamEvent {
97         public final long mStartTime;
98         public final long mStopTime;
99         public final UpstreamType mUpstreamType;
100 
RecordUpstreamEvent(final long startTime, final long stopTime, final UpstreamType upstream)101         RecordUpstreamEvent(final long startTime, final long stopTime,
102                 final UpstreamType upstream) {
103             mStartTime = startTime;
104             mStopTime = stopTime;
105             mUpstreamType = upstream;
106         }
107     }
108 
109     /**
110      * Creates a |NetworkTetheringReported.Builder| object to update the tethering stats for the
111      * specified downstream type and caller's package name. Initializes the upstream events, error
112      * code, and duration to default values. Sets the start time for the downstream type in the
113      * |mDownstreamStartTime| map.
114      * @param downstreamType The type of downstream connection (e.g. Wifi, USB, Bluetooth).
115      * @param callerPkg The package name of the caller.
116      */
createBuilder(final int downstreamType, final String callerPkg)117     public void createBuilder(final int downstreamType, final String callerPkg) {
118         NetworkTetheringReported.Builder statsBuilder = NetworkTetheringReported.newBuilder()
119                 .setDownstreamType(downstreamTypeToEnum(downstreamType))
120                 .setUserType(userTypeToEnum(callerPkg))
121                 .setUpstreamType(UpstreamType.UT_UNKNOWN)
122                 .setErrorCode(ErrorCode.EC_NO_ERROR)
123                 .setUpstreamEvents(UpstreamEvents.newBuilder())
124                 .setDurationMillis(0);
125         mBuilderMap.put(downstreamType, statsBuilder);
126         mDownstreamStartTime.put(downstreamType, timeNow());
127     }
128 
129     /**
130      * Update the error code of the given downstream type in the Tethering stats.
131      * @param downstreamType The downstream type whose error code to update.
132      * @param errCode The error code to set.
133      */
updateErrorCode(final int downstreamType, final int errCode)134     public void updateErrorCode(final int downstreamType, final int errCode) {
135         NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType);
136         if (statsBuilder == null) {
137             Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
138             return;
139         }
140         statsBuilder.setErrorCode(errorCodeToEnum(errCode));
141     }
142 
143     /**
144      * Update the list of upstream types and their duration whenever the current upstream type
145      * changes.
146      * @param ns The UpstreamNetworkState object representing the current upstream network state.
147      */
maybeUpdateUpstreamType(@ullable final UpstreamNetworkState ns)148     public void maybeUpdateUpstreamType(@Nullable final UpstreamNetworkState ns) {
149         UpstreamType upstream = transportTypeToUpstreamTypeEnum(ns);
150         if (upstream.equals(mCurrentUpstream)) return;
151 
152         final long newTime = timeNow();
153         if (mCurrentUpstream != null) {
154             mUpstreamEventList.add(new RecordUpstreamEvent(mCurrentUpStreamStartTime, newTime,
155                     mCurrentUpstream));
156         }
157         mCurrentUpstream = upstream;
158         mCurrentUpStreamStartTime = newTime;
159     }
160 
161     /**
162      * Updates the upstream events builder with a new upstream event.
163      * @param upstreamEventsBuilder the builder for the upstream events list
164      * @param start the start time of the upstream event
165      * @param stop the stop time of the upstream event
166      * @param upstream the type of upstream type (e.g. Wifi, Cellular, Bluetooth, ...)
167      */
addUpstreamEvent(final UpstreamEvents.Builder upstreamEventsBuilder, final long start, final long stop, @Nullable final UpstreamType upstream, final long txBytes, final long rxBytes)168     private void addUpstreamEvent(final UpstreamEvents.Builder upstreamEventsBuilder,
169             final long start, final long stop, @Nullable final UpstreamType upstream,
170             final long txBytes, final long rxBytes) {
171         final UpstreamEvent.Builder upstreamEventBuilder = UpstreamEvent.newBuilder()
172                 .setUpstreamType(upstream == null ? UpstreamType.UT_NO_NETWORK : upstream)
173                 .setDurationMillis(stop - start)
174                 .setTxBytes(txBytes)
175                 .setRxBytes(rxBytes);
176         upstreamEventsBuilder.addUpstreamEvent(upstreamEventBuilder);
177     }
178 
179     /**
180      * Updates the |NetworkTetheringReported.Builder| with relevant upstream events associated with
181      * the downstream event identified by the given downstream start time.
182      *
183      * This method iterates through the list of upstream events and adds any relevant events to a
184      * |UpstreamEvents.Builder|. Upstream events are considered relevant if their stop time is
185      * greater than or equal to the given downstream start time. The method also adds the last
186      * upstream event that occurred up until the current time.
187      *
188      * The resulting |UpstreamEvents.Builder| is then added to the
189      * |NetworkTetheringReported.Builder|, along with the duration of the downstream event
190      * (i.e., stop time minus downstream start time).
191      *
192      * @param statsBuilder the builder for the NetworkTetheringReported message
193      * @param downstreamStartTime the start time of the downstream event to find relevant upstream
194      * events for
195      */
noteDownstreamStopped(final NetworkTetheringReported.Builder statsBuilder, final long downstreamStartTime)196     private void noteDownstreamStopped(final NetworkTetheringReported.Builder statsBuilder,
197                     final long downstreamStartTime) {
198         UpstreamEvents.Builder upstreamEventsBuilder = UpstreamEvents.newBuilder();
199 
200         for (RecordUpstreamEvent event : mUpstreamEventList) {
201             if (downstreamStartTime > event.mStopTime) continue;
202 
203             final long startTime = Math.max(downstreamStartTime, event.mStartTime);
204             // Handle completed upstream events.
205             addUpstreamEvent(upstreamEventsBuilder, startTime, event.mStopTime,
206                     event.mUpstreamType, 0L /* txBytes */, 0L /* rxBytes */);
207         }
208         final long startTime = Math.max(downstreamStartTime, mCurrentUpStreamStartTime);
209         final long stopTime = timeNow();
210         // Handle the last upstream event.
211         addUpstreamEvent(upstreamEventsBuilder, startTime, stopTime, mCurrentUpstream,
212                 0L /* txBytes */, 0L /* rxBytes */);
213         statsBuilder.setUpstreamEvents(upstreamEventsBuilder);
214         statsBuilder.setDurationMillis(stopTime - downstreamStartTime);
215     }
216 
217     /**
218      * Removes tethering statistics for the given downstream type. If there are any stats to write
219      * for the downstream event associated with the type, they are written before removing the
220      * statistics.
221      *
222      * If the given downstream type does not exist in the map, an error message is logged and the
223      * method returns without doing anything.
224      *
225      * @param downstreamType the type of downstream event to remove statistics for
226      */
sendReport(final int downstreamType)227     public void sendReport(final int downstreamType) {
228         final NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType);
229         if (statsBuilder == null) {
230             Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
231             return;
232         }
233 
234         noteDownstreamStopped(statsBuilder, mDownstreamStartTime.get(downstreamType));
235         write(statsBuilder.build());
236 
237         mBuilderMap.remove(downstreamType);
238         mDownstreamStartTime.remove(downstreamType);
239     }
240 
241     /**
242      * Collects tethering statistics and writes them to the statsd pipeline. This method takes in a
243      * NetworkTetheringReported object, extracts its fields and uses them to write statistics data
244      * to the statsd pipeline.
245      *
246      * @param reported a NetworkTetheringReported object containing statistics to write
247      */
248     @VisibleForTesting
write(@onNull final NetworkTetheringReported reported)249     public void write(@NonNull final NetworkTetheringReported reported) {
250         final byte[] upstreamEvents = reported.getUpstreamEvents().toByteArray();
251 
252         TetheringStatsLog.write(
253                 TetheringStatsLog.NETWORK_TETHERING_REPORTED,
254                 reported.getErrorCode().getNumber(),
255                 reported.getDownstreamType().getNumber(),
256                 reported.getUpstreamType().getNumber(),
257                 reported.getUserType().getNumber(),
258                 upstreamEvents,
259                 reported.getDurationMillis());
260         if (DBG) {
261             Log.d(
262                     TAG,
263                     "Write errorCode: "
264                     + reported.getErrorCode().getNumber()
265                     + ", downstreamType: "
266                     + reported.getDownstreamType().getNumber()
267                     + ", upstreamType: "
268                     + reported.getUpstreamType().getNumber()
269                     + ", userType: "
270                     + reported.getUserType().getNumber()
271                     + ", upstreamTypes: "
272                     + Arrays.toString(upstreamEvents)
273                     + ", durationMillis: "
274                     + reported.getDurationMillis());
275         }
276     }
277 
278     /**
279      * Cleans up the variables related to upstream events when tethering is turned off.
280      */
cleanup()281     public void cleanup() {
282         mUpstreamEventList.clear();
283         mCurrentUpstream = null;
284         mCurrentUpStreamStartTime = 0L;
285     }
286 
downstreamTypeToEnum(final int ifaceType)287     private DownstreamType downstreamTypeToEnum(final int ifaceType) {
288         switch(ifaceType) {
289             case TETHERING_WIFI:
290                 return DownstreamType.DS_TETHERING_WIFI;
291             case TETHERING_WIFI_P2P:
292                 return DownstreamType.DS_TETHERING_WIFI_P2P;
293             case TETHERING_USB:
294                 return DownstreamType.DS_TETHERING_USB;
295             case TETHERING_BLUETOOTH:
296                 return DownstreamType.DS_TETHERING_BLUETOOTH;
297             case TETHERING_NCM:
298                 return DownstreamType.DS_TETHERING_NCM;
299             case TETHERING_ETHERNET:
300                 return DownstreamType.DS_TETHERING_ETHERNET;
301             default:
302                 return DownstreamType.DS_UNSPECIFIED;
303         }
304     }
305 
errorCodeToEnum(final int lastError)306     private ErrorCode errorCodeToEnum(final int lastError) {
307         switch(lastError) {
308             case TETHER_ERROR_NO_ERROR:
309                 return ErrorCode.EC_NO_ERROR;
310             case TETHER_ERROR_UNKNOWN_IFACE:
311                 return ErrorCode.EC_UNKNOWN_IFACE;
312             case TETHER_ERROR_SERVICE_UNAVAIL:
313                 return ErrorCode.EC_SERVICE_UNAVAIL;
314             case TETHER_ERROR_UNSUPPORTED:
315                 return ErrorCode.EC_UNSUPPORTED;
316             case TETHER_ERROR_UNAVAIL_IFACE:
317                 return ErrorCode.EC_UNAVAIL_IFACE;
318             case TETHER_ERROR_INTERNAL_ERROR:
319                 return ErrorCode.EC_INTERNAL_ERROR;
320             case TETHER_ERROR_TETHER_IFACE_ERROR:
321                 return ErrorCode.EC_TETHER_IFACE_ERROR;
322             case TETHER_ERROR_UNTETHER_IFACE_ERROR:
323                 return ErrorCode.EC_UNTETHER_IFACE_ERROR;
324             case TETHER_ERROR_ENABLE_FORWARDING_ERROR:
325                 return ErrorCode.EC_ENABLE_FORWARDING_ERROR;
326             case TETHER_ERROR_DISABLE_FORWARDING_ERROR:
327                 return ErrorCode.EC_DISABLE_FORWARDING_ERROR;
328             case TETHER_ERROR_IFACE_CFG_ERROR:
329                 return ErrorCode.EC_IFACE_CFG_ERROR;
330             case TETHER_ERROR_PROVISIONING_FAILED:
331                 return ErrorCode.EC_PROVISIONING_FAILED;
332             case TETHER_ERROR_DHCPSERVER_ERROR:
333                 return ErrorCode.EC_DHCPSERVER_ERROR;
334             case TETHER_ERROR_ENTITLEMENT_UNKNOWN:
335                 return ErrorCode.EC_ENTITLEMENT_UNKNOWN;
336             case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION:
337                 return ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION;
338             case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION:
339                 return ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION;
340             default:
341                 return ErrorCode.EC_UNKNOWN_TYPE;
342         }
343     }
344 
userTypeToEnum(final String callerPkg)345     private UserType userTypeToEnum(final String callerPkg) {
346         if (callerPkg.equals(SETTINGS_PKG_NAME)) {
347             return UserType.USER_SETTINGS;
348         } else if (callerPkg.equals(SYSTEMUI_PKG_NAME)) {
349             return UserType.USER_SYSTEMUI;
350         } else if (callerPkg.equals(GMS_PKG_NAME)) {
351             return UserType.USER_GMS;
352         } else {
353             return UserType.USER_UNKNOWN;
354         }
355     }
356 
transportTypeToUpstreamTypeEnum(final UpstreamNetworkState ns)357     private UpstreamType transportTypeToUpstreamTypeEnum(final UpstreamNetworkState ns) {
358         final NetworkCapabilities nc = (ns != null) ? ns.networkCapabilities : null;
359         if (nc == null) return UpstreamType.UT_NO_NETWORK;
360 
361         final int typeCount = nc.getTransportTypes().length;
362         // It's possible for a VCN network to be mapped to UT_UNKNOWN, as it may consist of both
363         // Wi-Fi and cellular transport.
364         // TODO: It's necessary to define a new upstream type for VCN, which can be identified by
365         // NET_CAPABILITY_NOT_VCN_MANAGED.
366         if (typeCount > 1) return UpstreamType.UT_UNKNOWN;
367 
368         if (nc.hasTransport(TRANSPORT_CELLULAR)) return UpstreamType.UT_CELLULAR;
369         if (nc.hasTransport(TRANSPORT_WIFI)) return UpstreamType.UT_WIFI;
370         if (nc.hasTransport(TRANSPORT_BLUETOOTH)) return UpstreamType.UT_BLUETOOTH;
371         if (nc.hasTransport(TRANSPORT_ETHERNET)) return UpstreamType.UT_ETHERNET;
372         if (nc.hasTransport(TRANSPORT_WIFI_AWARE)) return UpstreamType.UT_WIFI_AWARE;
373         if (nc.hasTransport(TRANSPORT_LOWPAN)) return UpstreamType.UT_LOWPAN;
374 
375         return UpstreamType.UT_UNKNOWN;
376     }
377 }
378