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.telephony.CellInfo;
21 import android.telephony.CellSignalStrength;
22 import android.telephony.CellSignalStrengthLte;
23 import android.telephony.NetworkRegistrationInfo;
24 import android.telephony.ServiceState;
25 import android.telephony.SignalStrength;
26 import android.telephony.TelephonyManager;
27 import android.util.Log;
28 import android.util.SparseArray;
29 
30 import com.android.internal.telephony.MccTable;
31 import com.android.internal.telephony.Phone;
32 import com.android.internal.telephony.metrics.SatelliteStats;
33 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
34 import com.android.internal.telephony.subscription.SubscriptionManagerService;
35 
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.List;
39 
40 public class CarrierRoamingSatelliteSessionStats {
41     private static final String TAG = CarrierRoamingSatelliteSessionStats.class.getSimpleName();
42     private static final SparseArray<CarrierRoamingSatelliteSessionStats>
43             sCarrierRoamingSatelliteSessionStats = new SparseArray<>();
44     @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
45     private int mCarrierId;
46     private boolean mIsNtnRoamingInHomeCountry;
47     private int mCountOfIncomingSms;
48     private int mCountOfOutgoingSms;
49     private int mCountOfIncomingMms;
50     private int mCountOfOutgoingMms;
51     private long mIncomingMessageId;
52 
53     private int mSessionStartTimeSec;
54     private List<Long> mConnectionStartTimeList;
55     private List<Long> mConnectionEndTimeList;
56     private List<Integer> mRsrpList;
57     private List<Integer> mRssnrList;
58 
CarrierRoamingSatelliteSessionStats(int subId)59     public CarrierRoamingSatelliteSessionStats(int subId) {
60         logd("Create new CarrierRoamingSatelliteSessionStats. subId=" + subId);
61         initializeParams();
62         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
63     }
64 
65     /** Gets a CarrierRoamingSatelliteSessionStats instance. */
getInstance(int subId)66     public static CarrierRoamingSatelliteSessionStats getInstance(int subId) {
67         synchronized (sCarrierRoamingSatelliteSessionStats) {
68             if (sCarrierRoamingSatelliteSessionStats.get(subId) == null) {
69                 sCarrierRoamingSatelliteSessionStats.put(subId,
70                         new CarrierRoamingSatelliteSessionStats(subId));
71             }
72             return sCarrierRoamingSatelliteSessionStats.get(subId);
73         }
74     }
75 
76     /** Log carrier roaming satellite session start */
onSessionStart(int carrierId, Phone phone)77     public void onSessionStart(int carrierId, Phone phone) {
78         mCarrierId = carrierId;
79         mSessionStartTimeSec = getCurrentTimeInSec();
80         mIsNtnRoamingInHomeCountry = false;
81         onConnectionStart(phone);
82     }
83 
84     /** Log carrier roaming satellite connection start */
onConnectionStart(Phone phone)85     public void onConnectionStart(Phone phone) {
86         mConnectionStartTimeList.add(getCurrentTime());
87         updateNtnRoamingInHomeCountry(phone);
88     }
89 
90     /** Log carrier roaming satellite session end */
onSessionEnd()91     public void onSessionEnd() {
92         onConnectionEnd();
93         reportMetrics();
94         mIsNtnRoamingInHomeCountry = false;
95     }
96 
97     /** Log carrier roaming satellite connection end */
onConnectionEnd()98     public void onConnectionEnd() {
99         mConnectionEndTimeList.add(getCurrentTime());
100     }
101 
102     /** Log rsrp and rssnr when occurred the service state change with NTN is connected. */
onSignalStrength(Phone phone)103     public void onSignalStrength(Phone phone) {
104         CellSignalStrengthLte cellSignalStrengthLte = getCellSignalStrengthLte(phone);
105         int rsrp = cellSignalStrengthLte.getRsrp();
106         int rssnr = cellSignalStrengthLte.getRssnr();
107         if (rsrp == CellInfo.UNAVAILABLE) {
108             logd("onSignalStrength: rsrp unavailable");
109             return;
110         }
111         if (rssnr == CellInfo.UNAVAILABLE) {
112             logd("onSignalStrength: rssnr unavailable");
113             return;
114         }
115         mRsrpList.add(rsrp);
116         mRssnrList.add(rssnr);
117         logd("onSignalStrength : rsrp=" + rsrp + ", rssnr=" + rssnr);
118     }
119 
120     /** Log incoming sms success case */
onIncomingSms(int subId)121     public void onIncomingSms(int subId) {
122         if (!isNtnConnected()) {
123             return;
124         }
125         mCountOfIncomingSms += 1;
126         logd("onIncomingSms: subId=" + subId + ", count=" + mCountOfIncomingSms);
127     }
128 
129     /** Log outgoing sms success case */
onOutgoingSms(int subId)130     public void onOutgoingSms(int subId) {
131         if (!isNtnConnected()) {
132             return;
133         }
134         mCountOfOutgoingSms += 1;
135         logd("onOutgoingSms: subId=" + subId + ", count=" + mCountOfOutgoingSms);
136     }
137 
138     /** Log incoming or outgoing mms success case */
onMms(boolean isIncomingMms, long messageId)139     public void onMms(boolean isIncomingMms, long messageId) {
140         if (!isNtnConnected()) {
141             return;
142         }
143         if (isIncomingMms) {
144             mIncomingMessageId = messageId;
145             mCountOfIncomingMms += 1;
146             logd("onMms: messageId=" + messageId + ", countOfIncomingMms=" + mCountOfIncomingMms);
147         } else {
148             if (mIncomingMessageId == messageId) {
149                 logd("onMms: NotifyResponse ignore it.");
150                 mIncomingMessageId = 0;
151                 return;
152             }
153             mCountOfOutgoingMms += 1;
154             logd("onMms: countOfOutgoingMms=" + mCountOfOutgoingMms);
155         }
156     }
157 
reportMetrics()158     private void reportMetrics() {
159         int totalSatelliteModeTimeSec = mSessionStartTimeSec > 0
160                 ? getCurrentTimeInSec() - mSessionStartTimeSec : 0;
161         int numberOfSatelliteConnections = getNumberOfSatelliteConnections();
162         int avgDurationOfSatelliteConnectionSec = getAvgDurationOfSatelliteConnection(
163                 numberOfSatelliteConnections);
164 
165         List<Integer> connectionGapList = getSatelliteConnectionGapList(
166                 numberOfSatelliteConnections);
167         int satelliteConnectionGapMinSec = 0;
168         int satelliteConnectionGapMaxSec = 0;
169         if (!connectionGapList.isEmpty()) {
170             satelliteConnectionGapMinSec = Collections.min(connectionGapList);
171             satelliteConnectionGapMaxSec = Collections.max(connectionGapList);
172         }
173 
174         SatelliteStats.CarrierRoamingSatelliteSessionParams params =
175                 new SatelliteStats.CarrierRoamingSatelliteSessionParams.Builder()
176                         .setCarrierId(mCarrierId)
177                         .setIsNtnRoamingInHomeCountry(mIsNtnRoamingInHomeCountry)
178                         .setTotalSatelliteModeTimeSec(totalSatelliteModeTimeSec)
179                         .setNumberOfSatelliteConnections(numberOfSatelliteConnections)
180                         .setAvgDurationOfSatelliteConnectionSec(avgDurationOfSatelliteConnectionSec)
181                         .setSatelliteConnectionGapMinSec(satelliteConnectionGapMinSec)
182                         .setSatelliteConnectionGapAvgSec(getAvg(connectionGapList))
183                         .setSatelliteConnectionGapMaxSec(satelliteConnectionGapMaxSec)
184                         .setRsrpAvg(getAvg(mRsrpList))
185                         .setRsrpMedian(getMedian(mRsrpList))
186                         .setRssnrAvg(getAvg(mRssnrList))
187                         .setRssnrMedian(getMedian(mRssnrList))
188                         .setCountOfIncomingSms(mCountOfIncomingSms)
189                         .setCountOfOutgoingSms(mCountOfOutgoingSms)
190                         .setCountOfIncomingMms(mCountOfIncomingMms)
191                         .setCountOfOutgoingMms(mCountOfOutgoingMms)
192                         .build();
193         SatelliteStats.getInstance().onCarrierRoamingSatelliteSessionMetrics(params);
194         logd("reportMetrics: " + params);
195         initializeParams();
196     }
197 
initializeParams()198     private void initializeParams() {
199         mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
200         mIsNtnRoamingInHomeCountry = false;
201         mCountOfIncomingSms = 0;
202         mCountOfOutgoingSms = 0;
203         mCountOfIncomingMms = 0;
204         mCountOfOutgoingMms = 0;
205         mIncomingMessageId = 0;
206 
207         mSessionStartTimeSec = 0;
208         mConnectionStartTimeList = new ArrayList<>();
209         mConnectionEndTimeList = new ArrayList<>();
210         mRsrpList = new ArrayList<>();
211         mRssnrList = new ArrayList<>();
212         logd("initializeParams");
213     }
214 
getCellSignalStrengthLte(Phone phone)215     private CellSignalStrengthLte getCellSignalStrengthLte(Phone phone) {
216         SignalStrength signalStrength = phone.getSignalStrength();
217         List<CellSignalStrength> cellSignalStrengths = signalStrength.getCellSignalStrengths();
218         for (CellSignalStrength cellSignalStrength : cellSignalStrengths) {
219             if (cellSignalStrength instanceof CellSignalStrengthLte) {
220                 return (CellSignalStrengthLte) cellSignalStrength;
221             }
222         }
223 
224         return new CellSignalStrengthLte();
225     }
226 
getNumberOfSatelliteConnections()227     private int getNumberOfSatelliteConnections() {
228         return Math.min(mConnectionStartTimeList.size(), mConnectionEndTimeList.size());
229     }
230 
getAvgDurationOfSatelliteConnection(int numberOfSatelliteConnections)231     private int getAvgDurationOfSatelliteConnection(int numberOfSatelliteConnections) {
232         if (numberOfSatelliteConnections == 0) {
233             return 0;
234         }
235 
236         long totalConnectionsDuration = 0;
237         for (int i = 0; i < numberOfSatelliteConnections; i++) {
238             long endTime = mConnectionEndTimeList.get(i);
239             long startTime = mConnectionStartTimeList.get(i);
240             if (endTime >= startTime && startTime > 0) {
241                 totalConnectionsDuration += endTime - startTime;
242             }
243         }
244 
245         long avgConnectionDuration = totalConnectionsDuration / numberOfSatelliteConnections;
246         return (int) (avgConnectionDuration / 1000L);
247     }
248 
getSatelliteConnectionGapList(int numberOfSatelliteConnections)249     private List<Integer> getSatelliteConnectionGapList(int numberOfSatelliteConnections) {
250         if (numberOfSatelliteConnections == 0) {
251             return new ArrayList<>();
252         }
253 
254         List<Integer> connectionGapList = new ArrayList<>();
255         for (int i = 1; i < numberOfSatelliteConnections; i++) {
256             long prevConnectionEndTime = mConnectionEndTimeList.get(i - 1);
257             long currentConnectionStartTime = mConnectionStartTimeList.get(i);
258             if (currentConnectionStartTime > prevConnectionEndTime && prevConnectionEndTime > 0) {
259                 connectionGapList.add((int) (
260                         (currentConnectionStartTime - prevConnectionEndTime) / 1000));
261             }
262         }
263         return connectionGapList;
264     }
265 
getAvg(@onNull List<Integer> list)266     private int getAvg(@NonNull List<Integer> list) {
267         if (list.isEmpty()) {
268             return 0;
269         }
270 
271         int total = 0;
272         for (int num : list) {
273             total += num;
274         }
275 
276         return total / list.size();
277     }
278 
getMedian(@onNull List<Integer> list)279     private int getMedian(@NonNull List<Integer> list) {
280         if (list.isEmpty()) {
281             return 0;
282         }
283         int size = list.size();
284         if (size == 1) {
285             return list.get(0);
286         }
287 
288         Collections.sort(list);
289         return size % 2 == 0 ? (list.get(size / 2 - 1) + list.get(size / 2)) / 2
290                 : list.get(size / 2);
291     }
292 
getCurrentTimeInSec()293     private int getCurrentTimeInSec() {
294         return (int) (System.currentTimeMillis() / 1000);
295     }
296 
getCurrentTime()297     private long getCurrentTime() {
298         return System.currentTimeMillis();
299     }
300 
isNtnConnected()301     private boolean isNtnConnected() {
302         return mSessionStartTimeSec != 0;
303     }
304 
updateNtnRoamingInHomeCountry(Phone phone)305     private void updateNtnRoamingInHomeCountry(Phone phone) {
306         int subId = phone.getSubId();
307         ServiceState serviceState = phone.getServiceState();
308         if (serviceState == null) {
309             logd("ServiceState is null");
310             return;
311         }
312 
313         String satelliteRegisteredPlmn = "";
314         for (NetworkRegistrationInfo nri
315                 : serviceState.getNetworkRegistrationInfoList()) {
316             if (nri.isNonTerrestrialNetwork()) {
317                 satelliteRegisteredPlmn = nri.getRegisteredPlmn();
318             }
319         }
320 
321         SubscriptionInfoInternal subscriptionInfoInternal =
322                 mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
323         if (subscriptionInfoInternal == null) {
324             logd("SubscriptionInfoInternal is null");
325             return;
326         }
327         String simCountry = MccTable.countryCodeForMcc(subscriptionInfoInternal.getMcc());
328         String satelliteRegisteredCountry = MccTable.countryCodeForMcc(
329                 satelliteRegisteredPlmn.substring(0, 3));
330         if (simCountry.equalsIgnoreCase(satelliteRegisteredCountry)) {
331             mIsNtnRoamingInHomeCountry = false;
332         } else {
333             // If device is connected to roaming non-terrestrial network, update to true.
334             mIsNtnRoamingInHomeCountry = true;
335         }
336         logd("updateNtnRoamingInHomeCountry: mIsNtnRoamingInHomeCountry="
337                 + mIsNtnRoamingInHomeCountry);
338     }
339 
logd(@onNull String log)340     private void logd(@NonNull String log) {
341         Log.d(TAG, log);
342     }
343 
loge(@onNull String log)344     private void loge(@NonNull String log) {
345         Log.e(TAG, log);
346     }
347 }
348