• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.satellite.metrics;
18 
19 import android.annotation.NonNull;
20 import android.app.usage.NetworkStats;
21 import android.app.usage.NetworkStatsManager;
22 import android.content.Context;
23 import android.net.NetworkTemplate;
24 import android.os.SystemClock;
25 import android.telephony.CellInfo;
26 import android.telephony.CellSignalStrength;
27 import android.telephony.CellSignalStrengthLte;
28 import android.telephony.NetworkRegistrationInfo;
29 import android.telephony.ServiceState;
30 import android.telephony.SignalStrength;
31 import android.telephony.TelephonyManager;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import android.util.SparseArray;
35 
36 import com.android.internal.telephony.MccTable;
37 import com.android.internal.telephony.Phone;
38 import com.android.internal.telephony.metrics.SatelliteStats;
39 import com.android.internal.telephony.satellite.SatelliteConstants;
40 import com.android.internal.telephony.satellite.SatelliteServiceUtils;
41 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
42 import com.android.internal.telephony.subscription.SubscriptionManagerService;
43 
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.OptionalDouble;
49 import java.util.Set;
50 
51 public class CarrierRoamingSatelliteSessionStats {
52     private static final String TAG = CarrierRoamingSatelliteSessionStats.class.getSimpleName();
53     private static final SparseArray<CarrierRoamingSatelliteSessionStats>
54             sCarrierRoamingSatelliteSessionStats = new SparseArray<>();
55     @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
56     private int mCarrierId;
57     private boolean mIsNtnRoamingInHomeCountry;
58     private int mCountOfIncomingSms;
59     private int mCountOfOutgoingSms;
60     private int mCountOfIncomingMms;
61     private int mCountOfOutgoingMms;
62     private long mIncomingMessageId;
63     private int mSessionStartTimeSec;
64     private SatelliteConnectionTimes mSatelliteConnectionTimes;
65     private List<SatelliteConnectionTimes> mSatelliteConnectionTimesList;
66     private List<Integer> mRsrpList;
67     private List<Integer> mRssnrList;
68     private int[] mSupportedSatelliteServices;
69     private int mServiceDataPolicy;
70     private Phone mPhone;
71     private Context mContext;
72     private long mSatelliteDataConsumedBytes = 0L;
73     private long mDataUsageOnSessionStartBytes = 0L;
74 
CarrierRoamingSatelliteSessionStats(int subId)75     public CarrierRoamingSatelliteSessionStats(int subId) {
76         logd("Create new CarrierRoamingSatelliteSessionStats. subId=" + subId);
77         initializeParams();
78         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
79     }
80 
81     /** Gets a CarrierRoamingSatelliteSessionStats instance. */
getInstance(int subId)82     public static CarrierRoamingSatelliteSessionStats getInstance(int subId) {
83         synchronized (sCarrierRoamingSatelliteSessionStats) {
84             if (sCarrierRoamingSatelliteSessionStats.get(subId) == null) {
85                 sCarrierRoamingSatelliteSessionStats.put(subId,
86                         new CarrierRoamingSatelliteSessionStats(subId));
87             }
88             return sCarrierRoamingSatelliteSessionStats.get(subId);
89         }
90     }
91 
92     /** Log carrier roaming satellite session start */
onSessionStart(int carrierId, Phone phone, int[] supportedServices, int serviceDataPolicy)93     public void onSessionStart(int carrierId, Phone phone, int[] supportedServices,
94             int serviceDataPolicy) {
95         mPhone = phone;
96         mContext = mPhone.getContext();
97         mCarrierId = carrierId;
98         mSupportedSatelliteServices = supportedServices;
99         mServiceDataPolicy = serviceDataPolicy;
100         mSessionStartTimeSec = getElapsedRealtimeInSec();
101         mIsNtnRoamingInHomeCountry = false;
102         onConnectionStart(mPhone);
103         mDataUsageOnSessionStartBytes = getDataUsage();
104         logd("current data consumed: " + mDataUsageOnSessionStartBytes);
105     }
106 
107     /** Log carrier roaming satellite connection start */
onConnectionStart(Phone phone)108     public void onConnectionStart(Phone phone) {
109         mSatelliteConnectionTimes = new SatelliteConnectionTimes(getElapsedRealtime());
110         updateNtnRoamingInHomeCountry(phone);
111     }
112 
113     /** calculate total satellite data consumed at the session */
getDataUsage()114     private long getDataUsage() {
115         if (mContext == null) {
116             return 0L;
117         }
118 
119         NetworkStatsManager networkStatsManager =
120                 mContext.getSystemService(NetworkStatsManager.class);
121 
122         if (networkStatsManager != null) {
123             final NetworkTemplate.Builder builder =
124                     new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE);
125             final String subscriberId = mPhone.getSubscriberId();
126             logd("subscriber id for data consumed:" + subscriberId);
127 
128             if (!TextUtils.isEmpty(subscriberId)) {
129                 builder.setSubscriberIds(Set.of(subscriberId));
130                 // Consider data usage calculation of only metered capabilities / data network
131                 builder.setMeteredness(android.net.NetworkStats.METERED_YES);
132                 NetworkTemplate template = builder.build();
133                 final NetworkStats.Bucket ret = networkStatsManager
134                         .querySummaryForDevice(template, 0L, System.currentTimeMillis());
135                 return ret.getRxBytes() + ret.getTxBytes();
136             }
137         }
138         return 0L;
139     }
140 
141     /** Log carrier roaming satellite session end */
onSessionEnd(int subId)142     public void onSessionEnd(int subId) {
143         onConnectionEnd();
144         long dataUsageOnSessionEndBytes = getDataUsage();
145         logd("update data consumed: " + dataUsageOnSessionEndBytes);
146         if (dataUsageOnSessionEndBytes > 0L
147                 && dataUsageOnSessionEndBytes > mDataUsageOnSessionStartBytes) {
148             mSatelliteDataConsumedBytes =
149                     dataUsageOnSessionEndBytes - mDataUsageOnSessionStartBytes;
150         }
151         logd("satellite data consumed at session: " + mSatelliteDataConsumedBytes);
152         reportMetrics(subId);
153         mIsNtnRoamingInHomeCountry = false;
154         mSupportedSatelliteServices = new int[0];
155         mServiceDataPolicy = SatelliteConstants.SATELLITE_ENTITLEMENT_SERVICE_POLICY_UNKNOWN;
156         mSatelliteDataConsumedBytes = 0L;
157         mDataUsageOnSessionStartBytes = 0L;
158     }
159 
160     /** Log carrier roaming satellite connection end */
onConnectionEnd()161     public void onConnectionEnd() {
162         if (mSatelliteConnectionTimes != null) {
163             mSatelliteConnectionTimes.setEndTime(getElapsedRealtime());
164             mSatelliteConnectionTimesList.add(mSatelliteConnectionTimes);
165             mSatelliteConnectionTimes = null;
166         } else {
167             loge("onConnectionEnd: mSatelliteConnectionTimes is null");
168         }
169     }
170 
171     /** Log rsrp and rssnr when occurred the service state change with NTN is connected. */
onSignalStrength(Phone phone)172     public void onSignalStrength(Phone phone) {
173         CellSignalStrengthLte cellSignalStrengthLte = getCellSignalStrengthLte(phone);
174         int rsrp = cellSignalStrengthLte.getRsrp();
175         int rssnr = cellSignalStrengthLte.getRssnr();
176         if (rsrp == CellInfo.UNAVAILABLE) {
177             logd("onSignalStrength: rsrp unavailable");
178             return;
179         }
180         if (rssnr == CellInfo.UNAVAILABLE) {
181             logd("onSignalStrength: rssnr unavailable");
182             return;
183         }
184         mRsrpList.add(rsrp);
185         mRssnrList.add(rssnr);
186         logd("onSignalStrength : rsrp=" + rsrp + ", rssnr=" + rssnr);
187     }
188 
189     /** Log incoming sms success case */
onIncomingSms(int subId)190     public void onIncomingSms(int subId) {
191         if (!isNtnConnected()) {
192             return;
193         }
194         mCountOfIncomingSms += 1;
195         logd("onIncomingSms: subId=" + subId + ", count=" + mCountOfIncomingSms);
196     }
197 
198     /** Log outgoing sms success case */
onOutgoingSms(int subId)199     public void onOutgoingSms(int subId) {
200         if (!isNtnConnected()) {
201             return;
202         }
203         mCountOfOutgoingSms += 1;
204         logd("onOutgoingSms: subId=" + subId + ", count=" + mCountOfOutgoingSms);
205     }
206 
207     /** Log incoming or outgoing mms success case */
onMms(boolean isIncomingMms, long messageId)208     public void onMms(boolean isIncomingMms, long messageId) {
209         if (!isNtnConnected()) {
210             return;
211         }
212         if (isIncomingMms) {
213             mIncomingMessageId = messageId;
214             mCountOfIncomingMms += 1;
215             logd("onMms: messageId=" + messageId + ", countOfIncomingMms=" + mCountOfIncomingMms);
216         } else {
217             if (mIncomingMessageId == messageId) {
218                 logd("onMms: NotifyResponse ignore it.");
219                 mIncomingMessageId = 0;
220                 return;
221             }
222             mCountOfOutgoingMms += 1;
223             logd("onMms: countOfOutgoingMms=" + mCountOfOutgoingMms);
224         }
225     }
226 
reportMetrics(int subId)227     private void reportMetrics(int subId) {
228         int totalSatelliteModeTimeSec = mSessionStartTimeSec > 0
229                 ? getElapsedRealtimeInSec() - mSessionStartTimeSec : 0;
230         int numberOfSatelliteConnections = getNumberOfSatelliteConnections();
231 
232         List<Integer> connectionGapList = getSatelliteConnectionGapList(
233                 numberOfSatelliteConnections);
234         int satelliteConnectionGapMinSec = 0;
235         int satelliteConnectionGapMaxSec = 0;
236         if (!connectionGapList.isEmpty()) {
237             satelliteConnectionGapMinSec = Collections.min(connectionGapList);
238             satelliteConnectionGapMaxSec = Collections.max(connectionGapList);
239         }
240         boolean isMultiSim = mSubscriptionManagerService.getActiveSubIdList(true).length > 1;
241 
242         SatelliteStats.CarrierRoamingSatelliteSessionParams params =
243                 new SatelliteStats.CarrierRoamingSatelliteSessionParams.Builder()
244                         .setCarrierId(mCarrierId)
245                         .setIsNtnRoamingInHomeCountry(mIsNtnRoamingInHomeCountry)
246                         .setTotalSatelliteModeTimeSec(totalSatelliteModeTimeSec)
247                         .setNumberOfSatelliteConnections(numberOfSatelliteConnections)
248                         .setAvgDurationOfSatelliteConnectionSec(
249                                 getAvgDurationOfSatelliteConnection())
250                         .setSatelliteConnectionGapMinSec(satelliteConnectionGapMinSec)
251                         .setSatelliteConnectionGapAvgSec(getAvg(connectionGapList))
252                         .setSatelliteConnectionGapMaxSec(satelliteConnectionGapMaxSec)
253                         .setRsrpAvg(getAvg(mRsrpList))
254                         .setRsrpMedian(getMedian(mRsrpList))
255                         .setRssnrAvg(getAvg(mRssnrList))
256                         .setRssnrMedian(getMedian(mRssnrList))
257                         .setCountOfIncomingSms(mCountOfIncomingSms)
258                         .setCountOfOutgoingSms(mCountOfOutgoingSms)
259                         .setCountOfIncomingMms(mCountOfIncomingMms)
260                         .setCountOfOutgoingMms(mCountOfOutgoingMms)
261                         .setSupportedSatelliteServices(mSupportedSatelliteServices)
262                         .setServiceDataPolicy(mServiceDataPolicy)
263                         .setSatelliteDataConsumedBytes(mSatelliteDataConsumedBytes)
264                         .setIsMultiSim(isMultiSim)
265                         .setIsNbIotNtn(SatelliteServiceUtils.isNbIotNtn(subId))
266                         .build();
267         SatelliteStats.getInstance().onCarrierRoamingSatelliteSessionMetrics(params);
268         logd("Supported satellite services: " + Arrays.toString(mSupportedSatelliteServices));
269         logd("reportMetrics: " + params);
270         initializeParams();
271     }
272 
initializeParams()273     private void initializeParams() {
274         mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
275         mIsNtnRoamingInHomeCountry = false;
276         mCountOfIncomingSms = 0;
277         mCountOfOutgoingSms = 0;
278         mCountOfIncomingMms = 0;
279         mCountOfOutgoingMms = 0;
280         mIncomingMessageId = 0;
281 
282         mSessionStartTimeSec = 0;
283         mSatelliteConnectionTimes = null;
284         mSatelliteConnectionTimesList = new ArrayList<>();
285         mRsrpList = new ArrayList<>();
286         mRssnrList = new ArrayList<>();
287         logd("initializeParams");
288     }
289 
getCellSignalStrengthLte(Phone phone)290     private CellSignalStrengthLte getCellSignalStrengthLte(Phone phone) {
291         SignalStrength signalStrength = phone.getSignalStrength();
292         List<CellSignalStrength> cellSignalStrengths = signalStrength.getCellSignalStrengths();
293         for (CellSignalStrength cellSignalStrength : cellSignalStrengths) {
294             if (cellSignalStrength instanceof CellSignalStrengthLte) {
295                 return (CellSignalStrengthLte) cellSignalStrength;
296             }
297         }
298 
299         return new CellSignalStrengthLte();
300     }
301 
getNumberOfSatelliteConnections()302     private int getNumberOfSatelliteConnections() {
303         return mSatelliteConnectionTimesList.size();
304     }
305 
getAvgDurationOfSatelliteConnection()306     private int getAvgDurationOfSatelliteConnection() {
307         if (mSatelliteConnectionTimesList.isEmpty()) {
308             return 0;
309         }
310 
311         OptionalDouble averageDuration = mSatelliteConnectionTimesList.stream()
312                 .filter(SatelliteConnectionTimes::isValid)
313                 .mapToLong(SatelliteConnectionTimes::getDuration)
314                 .average();
315 
316         return (int) (averageDuration.isPresent() ? averageDuration.getAsDouble() / 1000 : 0);
317     }
318 
getSatelliteConnectionGapList(int numberOfSatelliteConnections)319     private List<Integer> getSatelliteConnectionGapList(int numberOfSatelliteConnections) {
320         if (mSatelliteConnectionTimesList.size() < 2) {
321             return new ArrayList<>();
322         }
323 
324         List<Integer> connectionGapList = new ArrayList<>();
325         for (int i = 1; i < mSatelliteConnectionTimesList.size(); i++) {
326             SatelliteConnectionTimes prevConnection =
327                     mSatelliteConnectionTimesList.get(i - 1);
328             SatelliteConnectionTimes currentConnection =
329                     mSatelliteConnectionTimesList.get(i);
330 
331             if (prevConnection.getEndTime() > 0
332                     && currentConnection.getStartTime() > prevConnection.getEndTime()) {
333                 int gap = (int) ((currentConnection.getStartTime() - prevConnection.getEndTime())
334                         / 1000);
335                 connectionGapList.add(gap);
336             }
337         }
338         return connectionGapList;
339     }
340 
getAvg(@onNull List<Integer> list)341     private int getAvg(@NonNull List<Integer> list) {
342         if (list.isEmpty()) {
343             return 0;
344         }
345 
346         int total = 0;
347         for (int num : list) {
348             total += num;
349         }
350 
351         return total / list.size();
352     }
353 
getMedian(@onNull List<Integer> list)354     private int getMedian(@NonNull List<Integer> list) {
355         if (list.isEmpty()) {
356             return 0;
357         }
358         int size = list.size();
359         if (size == 1) {
360             return list.get(0);
361         }
362 
363         Collections.sort(list);
364         return size % 2 == 0 ? (list.get(size / 2 - 1) + list.get(size / 2)) / 2
365                 : list.get(size / 2);
366     }
367 
getElapsedRealtimeInSec()368     private int getElapsedRealtimeInSec() {
369         return (int) (getElapsedRealtime() / 1000);
370     }
371 
getElapsedRealtime()372     private long getElapsedRealtime() {
373         return SystemClock.elapsedRealtime();
374     }
375 
isNtnConnected()376     private boolean isNtnConnected() {
377         return mSessionStartTimeSec != 0;
378     }
379 
updateNtnRoamingInHomeCountry(Phone phone)380     private void updateNtnRoamingInHomeCountry(Phone phone) {
381         int subId = phone.getSubId();
382         ServiceState serviceState = phone.getServiceState();
383         if (serviceState == null) {
384             logd("ServiceState is null");
385             return;
386         }
387 
388         String satelliteRegisteredPlmn = "";
389         for (NetworkRegistrationInfo nri
390                 : serviceState.getNetworkRegistrationInfoList()) {
391             if (nri.isNonTerrestrialNetwork()) {
392                 satelliteRegisteredPlmn = nri.getRegisteredPlmn();
393             }
394         }
395 
396         SubscriptionInfoInternal subscriptionInfoInternal =
397                 mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
398         if (subscriptionInfoInternal == null) {
399             logd("SubscriptionInfoInternal is null");
400             return;
401         }
402         String simCountry = MccTable.countryCodeForMcc(subscriptionInfoInternal.getMcc());
403         mIsNtnRoamingInHomeCountry = true;
404         if (satelliteRegisteredPlmn != null
405                 && satelliteRegisteredPlmn.length() >= 3) {
406             String satelliteRegisteredCountry = MccTable.countryCodeForMcc(
407                     satelliteRegisteredPlmn.substring(0, 3));
408             if (simCountry.equalsIgnoreCase(satelliteRegisteredCountry)) {
409                 mIsNtnRoamingInHomeCountry = true;
410             } else {
411                 // If device is connected to roaming non-terrestrial network, then marking as
412                 // roaming in external country
413                 mIsNtnRoamingInHomeCountry = false;
414             }
415         }
416         logd("updateNtnRoamingInHomeCountry: mIsNtnRoamingInHomeCountry="
417                 + mIsNtnRoamingInHomeCountry);
418     }
419 
420     private static class SatelliteConnectionTimes {
421         private final long mStartTime;
422         private long mEndTime;
423 
SatelliteConnectionTimes(long startTime)424         SatelliteConnectionTimes(long startTime) {
425             this.mStartTime = startTime;
426             this.mEndTime = 0;
427         }
428 
setEndTime(long endTime)429         public void setEndTime(long endTime) {
430             this.mEndTime = endTime;
431         }
432 
getStartTime()433         public long getStartTime() {
434             return mStartTime;
435         }
436 
getEndTime()437         public long getEndTime() {
438             return mEndTime;
439         }
440 
getDuration()441         public long getDuration() {
442             if (isValid()) {
443                 return mEndTime - mStartTime;
444             }
445             return 0;
446         }
447 
isValid()448         public boolean isValid() {
449             return mEndTime > mStartTime && mStartTime > 0;
450         }
451     }
452 
logd(@onNull String log)453     private void logd(@NonNull String log) {
454         Log.d(TAG, log);
455     }
456 
loge(@onNull String log)457     private void loge(@NonNull String log) {
458         Log.e(TAG, log);
459     }
460 }
461