• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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;
18 
19 import static android.telephony.ServiceState.STATE_EMERGENCY_ONLY;
20 import static android.telephony.ServiceState.STATE_IN_SERVICE;
21 
22 import static java.util.stream.Collectors.joining;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.content.Context;
27 import android.os.AsyncResult;
28 import android.os.Binder;
29 import android.os.PersistableBundle;
30 import android.telephony.AccessNetworkConstants;
31 import android.telephony.CarrierConfigManager;
32 import android.telephony.CellIdentity;
33 import android.telephony.DropBoxManagerLoggerBackend;
34 import android.telephony.NetworkRegistrationInfo;
35 import android.telephony.PersistentLogger;
36 import android.telephony.ServiceState;
37 import android.telephony.SubscriptionInfo;
38 import android.telephony.SubscriptionManager;
39 import android.telephony.satellite.AntennaPosition;
40 import android.telephony.satellite.EarfcnRange;
41 import android.telephony.satellite.NtnSignalStrength;
42 import android.telephony.satellite.PointingInfo;
43 import android.telephony.satellite.SatelliteCapabilities;
44 import android.telephony.satellite.SatelliteDatagram;
45 import android.telephony.satellite.SatelliteInfo;
46 import android.telephony.satellite.SatelliteManager;
47 import android.telephony.satellite.SatelliteModemEnableRequestAttributes;
48 import android.telephony.satellite.SatelliteSubscriptionInfo;
49 import android.telephony.satellite.SystemSelectionSpecifier;
50 import android.telephony.satellite.stub.NTRadioTechnology;
51 import android.telephony.satellite.stub.SatelliteModemState;
52 import android.telephony.satellite.stub.SatelliteResult;
53 import android.text.TextUtils;
54 import android.util.Log;
55 
56 import com.android.internal.R;
57 import com.android.internal.telephony.CommandException;
58 import com.android.internal.telephony.Phone;
59 import com.android.internal.telephony.PhoneFactory;
60 import com.android.internal.telephony.subscription.SubscriptionManagerService;
61 import com.android.internal.telephony.util.TelephonyUtils;
62 
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.HashMap;
66 import java.util.HashSet;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Set;
70 import java.util.stream.Collectors;
71 
72 /**
73  * Utils class for satellite service <-> framework conversions
74  */
75 public class SatelliteServiceUtils {
76     private static final String TAG = "SatelliteServiceUtils";
77 
78     /**
79      * Converts a carrier roaming NTN (Non-Terrestrial Network) connect type constant
80      * from {@link CarrierConfigManager} to string.
81      * @param type The carrier roaming NTN connect type constant.
82      * @return A string representation of the connect type, or "Unknown(type)" if not recognized.
83      */
carrierRoamingNtnConnectTypeToString( @arrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_TYPE int type)84     public static String carrierRoamingNtnConnectTypeToString(
85             @CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_TYPE int type) {
86         return switch (type) {
87             case CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC -> "AUTOMATIC";
88             case CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL -> "MANUAL";
89             default -> "Unknown(" + type + ")";
90         };
91     }
92 
93     /**
94      * Convert radio technology from service definition to framework definition.
95      * @param radioTechnology The NTRadioTechnology from the satellite service.
96      * @return The converted NTRadioTechnology for the framework.
97      */
98     @SatelliteManager.NTRadioTechnology
fromSatelliteRadioTechnology(int radioTechnology)99     public static int fromSatelliteRadioTechnology(int radioTechnology) {
100         switch (radioTechnology) {
101             case NTRadioTechnology.NB_IOT_NTN:
102                 return SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN;
103             case NTRadioTechnology.NR_NTN:
104                 return SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN;
105             case NTRadioTechnology.EMTC_NTN:
106                 return SatelliteManager.NT_RADIO_TECHNOLOGY_EMTC_NTN;
107             case NTRadioTechnology.PROPRIETARY:
108                 return SatelliteManager.NT_RADIO_TECHNOLOGY_PROPRIETARY;
109             default:
110                 loge("Received invalid radio technology: " + radioTechnology);
111                 return SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN;
112         }
113     }
114 
115     /**
116      * Convert satellite error from service definition to framework definition.
117      * @param error The SatelliteError from the satellite service.
118      * @return The converted SatelliteResult for the framework.
119      */
fromSatelliteError(int error)120     @SatelliteManager.SatelliteResult public static int fromSatelliteError(int error) {
121         switch (error) {
122             case SatelliteResult.SATELLITE_RESULT_SUCCESS:
123                 return SatelliteManager.SATELLITE_RESULT_SUCCESS;
124             case SatelliteResult.SATELLITE_RESULT_ERROR:
125                 return SatelliteManager.SATELLITE_RESULT_ERROR;
126             case SatelliteResult.SATELLITE_RESULT_SERVER_ERROR:
127                 return SatelliteManager.SATELLITE_RESULT_SERVER_ERROR;
128             case SatelliteResult.SATELLITE_RESULT_SERVICE_ERROR:
129                 return SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR;
130             case SatelliteResult.SATELLITE_RESULT_MODEM_ERROR:
131                 return SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
132             case SatelliteResult.SATELLITE_RESULT_NETWORK_ERROR:
133                 return SatelliteManager.SATELLITE_RESULT_NETWORK_ERROR;
134             case SatelliteResult.SATELLITE_RESULT_INVALID_MODEM_STATE:
135                 return SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE;
136             case SatelliteResult.SATELLITE_RESULT_INVALID_ARGUMENTS:
137                 return SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS;
138             case SatelliteResult.SATELLITE_RESULT_REQUEST_FAILED:
139                 return SatelliteManager.SATELLITE_RESULT_REQUEST_FAILED;
140             case SatelliteResult.SATELLITE_RESULT_RADIO_NOT_AVAILABLE:
141                 return SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE;
142             case SatelliteResult.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED:
143                 return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
144             case SatelliteResult.SATELLITE_RESULT_NO_RESOURCES:
145                 return SatelliteManager.SATELLITE_RESULT_NO_RESOURCES;
146             case SatelliteResult.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED:
147                 return SatelliteManager.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED;
148             case SatelliteResult.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS:
149                 return SatelliteManager.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS;
150             case SatelliteResult.SATELLITE_RESULT_REQUEST_ABORTED:
151                 return SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED;
152             case SatelliteResult.SATELLITE_RESULT_ACCESS_BARRED:
153                 return SatelliteManager.SATELLITE_RESULT_ACCESS_BARRED;
154             case SatelliteResult.SATELLITE_RESULT_NETWORK_TIMEOUT:
155                 return SatelliteManager.SATELLITE_RESULT_NETWORK_TIMEOUT;
156             case SatelliteResult.SATELLITE_RESULT_NOT_REACHABLE:
157                 return SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE;
158             case SatelliteResult.SATELLITE_RESULT_NOT_AUTHORIZED:
159                 return SatelliteManager.SATELLITE_RESULT_NOT_AUTHORIZED;
160         }
161         loge("Received invalid satellite service error: " + error);
162         return SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR;
163     }
164 
165     /**
166      * Convert satellite modem state from service definition to framework definition.
167      * @param modemState The SatelliteModemState from the satellite service.
168      * @return The converted SatelliteModemState for the framework.
169      */
170     @SatelliteManager.SatelliteModemState
fromSatelliteModemState(int modemState)171     public static int fromSatelliteModemState(int modemState) {
172         switch (modemState) {
173             case SatelliteModemState.SATELLITE_MODEM_STATE_IDLE:
174                 return SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
175             case SatelliteModemState.SATELLITE_MODEM_STATE_LISTENING:
176                 return SatelliteManager.SATELLITE_MODEM_STATE_LISTENING;
177             case SatelliteModemState.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING:
178                 return SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
179             case SatelliteModemState.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING:
180                 return SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING;
181             case SatelliteModemState.SATELLITE_MODEM_STATE_OFF:
182                 return SatelliteManager.SATELLITE_MODEM_STATE_OFF;
183             case SatelliteModemState.SATELLITE_MODEM_STATE_UNAVAILABLE:
184                 return SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
185             case SatelliteModemState.SATELLITE_MODEM_STATE_OUT_OF_SERVICE:
186                 return SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
187             case SatelliteModemState.SATELLITE_MODEM_STATE_IN_SERVICE:
188                 return SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
189             default:
190                 loge("Received invalid modem state: " + modemState);
191                 return SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
192         }
193     }
194 
195     /**
196      * Convert SatelliteCapabilities from service definition to framework definition.
197      * @param capabilities The SatelliteCapabilities from the satellite service.
198      * @return The converted SatelliteCapabilities for the framework.
199      */
fromSatelliteCapabilities( @ullable android.telephony.satellite.stub.SatelliteCapabilities capabilities)200     @Nullable public static SatelliteCapabilities fromSatelliteCapabilities(
201             @Nullable android.telephony.satellite.stub.SatelliteCapabilities capabilities) {
202         if (capabilities == null) return null;
203         int[] radioTechnologies = capabilities.supportedRadioTechnologies == null
204                 ? new int[0] : capabilities.supportedRadioTechnologies;
205 
206         Map<Integer, AntennaPosition> antennaPositionMap = new HashMap<>();
207         int[] antennaPositionKeys = capabilities.antennaPositionKeys;
208         AntennaPosition[] antennaPositionValues = capabilities.antennaPositionValues;
209         if (antennaPositionKeys != null && antennaPositionValues != null &&
210                 antennaPositionKeys.length == antennaPositionValues.length) {
211             for(int i = 0; i < antennaPositionKeys.length; i++) {
212                 antennaPositionMap.put(antennaPositionKeys[i], antennaPositionValues[i]);
213             }
214         }
215 
216         return new SatelliteCapabilities(
217                 Arrays.stream(radioTechnologies)
218                         .map(SatelliteServiceUtils::fromSatelliteRadioTechnology)
219                         .boxed().collect(Collectors.toSet()),
220                 capabilities.isPointingRequired, capabilities.maxBytesPerOutgoingDatagram,
221                 antennaPositionMap);
222     }
223 
224     /**
225      * Convert PointingInfo from service definition to framework definition.
226      * @param pointingInfo The PointingInfo from the satellite service.
227      * @return The converted PointingInfo for the framework.
228      */
fromPointingInfo( android.telephony.satellite.stub.PointingInfo pointingInfo)229     @Nullable public static PointingInfo fromPointingInfo(
230             android.telephony.satellite.stub.PointingInfo pointingInfo) {
231         if (pointingInfo == null) return null;
232         return new PointingInfo(pointingInfo.satelliteAzimuth, pointingInfo.satelliteElevation);
233     }
234 
235     /**
236      * Convert SatelliteDatagram from service definition to framework definition.
237      * @param datagram The SatelliteDatagram from the satellite service.
238      * @return The converted SatelliteDatagram for the framework.
239      */
fromSatelliteDatagram( android.telephony.satellite.stub.SatelliteDatagram datagram)240     @Nullable public static SatelliteDatagram fromSatelliteDatagram(
241             android.telephony.satellite.stub.SatelliteDatagram datagram) {
242         if (datagram == null) return null;
243         byte[] data = datagram.data == null ? new byte[0] : datagram.data;
244         return new SatelliteDatagram(data);
245     }
246 
247     /**
248      * Convert non-terrestrial signal strength from service definition to framework definition.
249      * @param ntnSignalStrength The non-terrestrial signal strength from the satellite service.
250      * @return The converted non-terrestrial signal strength for the framework.
251      */
fromNtnSignalStrength( android.telephony.satellite.stub.NtnSignalStrength ntnSignalStrength)252     @Nullable public static NtnSignalStrength fromNtnSignalStrength(
253             android.telephony.satellite.stub.NtnSignalStrength ntnSignalStrength) {
254         return new NtnSignalStrength(ntnSignalStrength.signalStrengthLevel);
255     }
256 
257     /**
258      * Convert SatelliteDatagram from framework definition to service definition.
259      * @param datagram The SatelliteDatagram from the framework.
260      * @return The converted SatelliteDatagram for the satellite service.
261      */
toSatelliteDatagram( @ullable SatelliteDatagram datagram)262     @Nullable public static android.telephony.satellite.stub.SatelliteDatagram toSatelliteDatagram(
263             @Nullable SatelliteDatagram datagram) {
264         android.telephony.satellite.stub.SatelliteDatagram converted =
265                 new android.telephony.satellite.stub.SatelliteDatagram();
266         converted.data = datagram.getSatelliteDatagram();
267         return converted;
268     }
269 
270     /**
271      * Convert SatelliteSubscriptionInfo from framework definition to service definition.
272      * @param info The SatelliteSubscriptionInfo from the framework.
273      * @return The converted SatelliteSubscriptionInfo for the satellite service.
274      */
275     @NonNull public static android.telephony.satellite.stub
toSatelliteSubscriptionInfo( @onNull SatelliteSubscriptionInfo info )276             .SatelliteSubscriptionInfo toSatelliteSubscriptionInfo(
277             @NonNull SatelliteSubscriptionInfo info
278     ) {
279         android.telephony.satellite.stub.SatelliteSubscriptionInfo converted =
280                 new android.telephony.satellite.stub.SatelliteSubscriptionInfo();
281         converted.iccId = info.getIccId();
282         converted.niddApn = info.getNiddApn();
283         return converted;
284     }
285 
286     /**
287      * Convert SatelliteModemEnableRequestAttributes from framework definition to service definition
288      * @param attributes The SatelliteModemEnableRequestAttributes from the framework.
289      * @return The converted SatelliteModemEnableRequestAttributes for the satellite service.
290      */
291     @NonNull public static android.telephony.satellite.stub
toSatelliteModemEnableRequestAttributes( @onNull SatelliteModemEnableRequestAttributes attributes )292             .SatelliteModemEnableRequestAttributes toSatelliteModemEnableRequestAttributes(
293             @NonNull SatelliteModemEnableRequestAttributes attributes
294     ) {
295         android.telephony.satellite.stub.SatelliteModemEnableRequestAttributes converted =
296                 new android.telephony.satellite.stub.SatelliteModemEnableRequestAttributes();
297         converted.isEnabled = attributes.isEnabled();
298         converted.isDemoMode = attributes.isForDemoMode();
299         converted.isEmergencyMode = attributes.isForEmergencyMode();
300         converted.satelliteSubscriptionInfo = toSatelliteSubscriptionInfo(
301                 attributes.getSatelliteSubscriptionInfo());
302         return converted;
303     }
304 
305     /**
306      * Get the {@link SatelliteManager.SatelliteResult} from the provided result.
307      *
308      * @param ar AsyncResult used to determine the error code.
309      * @param caller The satellite request.
310      *
311      * @return The {@link SatelliteManager.SatelliteResult} error code from the request.
312      */
getSatelliteError(@onNull AsyncResult ar, @NonNull String caller)313     @SatelliteManager.SatelliteResult public static int getSatelliteError(@NonNull AsyncResult ar,
314             @NonNull String caller) {
315         int errorCode;
316         if (ar.exception == null) {
317             errorCode = SatelliteManager.SATELLITE_RESULT_SUCCESS;
318         } else {
319             errorCode = SatelliteManager.SATELLITE_RESULT_ERROR;
320             if (ar.exception instanceof SatelliteManager.SatelliteException) {
321                 errorCode = ((SatelliteManager.SatelliteException) ar.exception).getErrorCode();
322                 loge(caller + " SatelliteException: " + ar.exception);
323             } else if (ar.exception instanceof CommandException) {
324                 errorCode = convertCommandExceptionErrorToSatelliteError(
325                         ((CommandException) ar.exception).getCommandError());
326                 loge(caller + " CommandException: "  + ar.exception);
327             } else {
328                 loge(caller + " unknown exception: " + ar.exception);
329             }
330         }
331         logd(caller + " error: " + errorCode);
332         return errorCode;
333     }
334 
convertCommandExceptionErrorToSatelliteError( CommandException.Error commandExceptionError)335     private static int convertCommandExceptionErrorToSatelliteError(
336             CommandException.Error commandExceptionError) {
337         logd("convertCommandExceptionErrorToSatelliteError: commandExceptionError="
338                 + commandExceptionError.toString());
339 
340         switch(commandExceptionError) {
341             case REQUEST_NOT_SUPPORTED:
342                 return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
343             case RADIO_NOT_AVAILABLE:
344                 return SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE;
345             case INTERNAL_ERR:
346             case INVALID_STATE:
347             case INVALID_MODEM_STATE:
348                 return SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE;
349             case MODEM_ERR:
350                 return SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
351             default:
352                 return SatelliteManager.SATELLITE_RESULT_ERROR;
353         }
354     }
355 
356     /**
357      * Get valid subscription id for satellite communication.
358      *
359      * @param subId The subscription id.
360      * @return input subId if the subscription is active else return default subscription id.
361      */
getValidSatelliteSubId(int subId, @NonNull Context context)362     public static int getValidSatelliteSubId(int subId, @NonNull Context context) {
363         final long identity = Binder.clearCallingIdentity();
364         try {
365             boolean isActive = SubscriptionManagerService.getInstance().isActiveSubId(subId,
366                     context.getOpPackageName(), context.getAttributionTag());
367 
368             if (isActive) {
369                 return subId;
370             }
371         } finally {
372             Binder.restoreCallingIdentity(identity);
373         }
374         logd("getValidSatelliteSubId: use DEFAULT_SUBSCRIPTION_ID for subId=" + subId);
375         return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
376     }
377 
378     /**
379      * Get the subscription ID which supports OEM based NTN satellite service.
380      *
381      * @return ID of the subscription that supports OEM-based satellite if any,
382      * return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} otherwise.
383      */
getNtnOnlySubscriptionId(@onNull Context context)384     public static int getNtnOnlySubscriptionId(@NonNull Context context) {
385         List<SubscriptionInfo> infoList =
386                 SubscriptionManagerService.getInstance().getAllSubInfoList(
387                         context.getOpPackageName(), null);
388 
389         int subId = infoList.stream()
390                 .filter(info -> info.isOnlyNonTerrestrialNetwork())
391                 .mapToInt(SubscriptionInfo::getSubscriptionId)
392                 .findFirst()
393                 .orElse(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
394         logd("getNtnOnlySubscriptionId: subId=" + subId);
395         return subId;
396     }
397 
398     /**
399      * Check if the subscription ID is a NTN only subscription ID.
400      *
401      * @return {@code true} if the subscription ID is a NTN only subscription ID,
402      * {@code false} otherwise.
403     */
isNtnOnlySubscriptionId(int subId)404     public static boolean isNtnOnlySubscriptionId(int subId) {
405         SubscriptionManagerService subscriptionManagerService =
406             SubscriptionManagerService.getInstance();
407         if (subscriptionManagerService == null) {
408             logd("isNtnOnlySubscriptionId: subscriptionManagerService is null");
409             return false;
410         }
411 
412         SubscriptionInfo subInfo = subscriptionManagerService.getSubscriptionInfo(subId);
413         if (subInfo == null) {
414             logd("isNtnOnlySubscriptionId: subInfo is null for subId=" + subId);
415             return false;
416         }
417 
418         return subInfo.isOnlyNonTerrestrialNetwork();
419     }
420 
421     /**
422      * Expected format of the input dictionary bundle is:
423      * <ul>
424      *     <li>Key: PLMN string.</li>
425      *     <li>Value: A string with format "service_1,service_2,..."</li>
426      * </ul>
427      * @return The map of supported services with key: PLMN, value: set of services supported by
428      * the PLMN.
429      */
430     @NonNull
431     @NetworkRegistrationInfo.ServiceType
parseSupportedSatelliteServices( PersistableBundle supportedServicesBundle)432     public static Map<String, Set<Integer>> parseSupportedSatelliteServices(
433             PersistableBundle supportedServicesBundle) {
434         Map<String, Set<Integer>> supportedServicesMap = new HashMap<>();
435         if (supportedServicesBundle == null || supportedServicesBundle.isEmpty()) {
436             return supportedServicesMap;
437         }
438 
439         for (String plmn : supportedServicesBundle.keySet()) {
440             if (TelephonyUtils.isValidPlmn(plmn)) {
441                 Set<Integer> supportedServicesSet = new HashSet<>();
442                 for (int serviceType : supportedServicesBundle.getIntArray(plmn)) {
443                     if (TelephonyUtils.isValidService(serviceType)) {
444                         supportedServicesSet.add(serviceType);
445                     } else {
446                         loge("parseSupportedSatelliteServices: invalid service type=" + serviceType
447                                 + " for plmn=" + plmn);
448                     }
449                 }
450                 logd("parseSupportedSatelliteServices: plmn=" + plmn + ", supportedServicesSet="
451                         + supportedServicesSet.stream().map(String::valueOf).collect(
452                         joining(",")));
453                 supportedServicesMap.put(plmn, supportedServicesSet);
454             } else {
455                 loge("parseSupportedSatelliteServices: invalid plmn=" + plmn);
456             }
457         }
458         return supportedServicesMap;
459     }
460 
461     /**
462      * Merge two string lists into one such that the result list does not have any duplicate items.
463      */
464     @NonNull
mergeStrLists(List<String> strList1, List<String> strList2)465     public static List<String> mergeStrLists(List<String> strList1, List<String> strList2) {
466         Set<String> mergedStrSet = new HashSet<>();
467         if (strList1 != null) {
468             mergedStrSet.addAll(strList1);
469         }
470 
471         if (strList2 != null) {
472             mergedStrSet.addAll(strList2);
473         }
474 
475         return mergedStrSet.stream().toList();
476     }
477 
478     /**
479      * Merge three string lists into one such that the result list does not have any duplicate
480      * items.
481      */
482     @NonNull
mergeStrLists(List<String> strList1, List<String> strList2, List<String> strList3)483     public static List<String> mergeStrLists(List<String> strList1, List<String> strList2,
484             List<String> strList3) {
485         Set<String> mergedStrSet = new HashSet<>();
486         mergedStrSet.addAll(strList1);
487         mergedStrSet.addAll(strList2);
488         mergedStrSet.addAll(strList3);
489         return mergedStrSet.stream().toList();
490     }
491 
492     /**
493      * Check if the datagramType is the sos message (DATAGRAM_TYPE_SOS_MESSAGE,
494      * DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
495      * DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED) or not
496      */
isSosMessage(int datagramType)497     public static boolean isSosMessage(int datagramType) {
498         return datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE
499                 || datagramType == SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP
500                 || datagramType == SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED;
501     }
502 
503     /**
504      * Check if the datagramType is the last sos message
505      * (DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP or
506      * DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED) or not
507      */
isLastSosMessage(int datagramType)508     public static boolean isLastSosMessage(int datagramType) {
509         return datagramType == SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP
510                 || datagramType == SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED;
511     }
512 
513     /**
514      * Return phone associated with phoneId 0.
515      *
516      * @return phone associated with phoneId 0 or {@code null} if it doesn't exist.
517      */
getPhone()518     public static @Nullable Phone getPhone() {
519         return PhoneFactory.getPhone(0);
520     }
521 
522     /**
523      * Return phone associated with subscription ID.
524      *
525      * @return phone associated with {@code subId} or {@code null} if it doesn't exist.
526      */
getPhone(int subId)527     public static @Nullable Phone getPhone(int subId) {
528         return PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
529     }
530 
531     /** Return {@code true} if device has cellular coverage, else return {@code false}. */
isCellularAvailable()532     public static boolean isCellularAvailable() {
533         for (Phone phone : PhoneFactory.getPhones()) {
534             ServiceState serviceState = phone.getServiceState();
535             if (serviceState != null) {
536                 int state = serviceState.getState();
537                 NetworkRegistrationInfo dataNri = serviceState.getNetworkRegistrationInfo(
538                         NetworkRegistrationInfo.DOMAIN_PS,
539                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
540                 boolean isCellularDataInService = dataNri != null && dataNri.isInService();
541                 logd("isCellularAvailable: phoneId=" + phone.getPhoneId() + " state=" + state
542                         + " isEmergencyOnly=" + serviceState.isEmergencyOnly()
543                         + " isCellularDataInService=" + isCellularDataInService);
544 
545                 if ((state == STATE_IN_SERVICE || state == STATE_EMERGENCY_ONLY
546                         || serviceState.isEmergencyOnly()
547                         || isCellularDataInService)
548                         && !isSatellitePlmn(phone.getSubId(), serviceState)) {
549                     logd("isCellularAvailable true");
550                     return true;
551                 }
552             }
553         }
554         logd("isCellularAvailable false");
555         return false;
556     }
557 
558     /** Check whether device is connected to satellite PLMN */
isSatellitePlmn(int subId, @NonNull ServiceState serviceState)559     public static boolean isSatellitePlmn(int subId, @NonNull ServiceState serviceState) {
560         List<String> satellitePlmnList = new ArrayList<>(
561                 SatelliteController.getInstance().getAllPlmnSet());
562         if (satellitePlmnList.isEmpty()) {
563             logd("isSatellitePlmn: satellitePlmnList is empty");
564             return false;
565         }
566 
567         for (NetworkRegistrationInfo nri :
568                 serviceState.getNetworkRegistrationInfoListForTransportType(
569                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) {
570             String registeredPlmn = nri.getRegisteredPlmn();
571             String mccmnc = getMccMnc(nri);
572             if (TextUtils.isEmpty(registeredPlmn) && TextUtils.isEmpty(mccmnc)) {
573                 logd("isSatellitePlmn: registeredPlmn and cell plmn are empty");
574                 continue;
575             }
576 
577             for (String satellitePlmn : satellitePlmnList) {
578                 if (TextUtils.equals(satellitePlmn, registeredPlmn)
579                         || TextUtils.equals(satellitePlmn, mccmnc)) {
580                     logd("isSatellitePlmn: return true, satellitePlmn:" + satellitePlmn
581                             + " registeredPlmn:" + registeredPlmn + " mccmnc:" + mccmnc);
582                     return true;
583                 }
584             }
585         }
586 
587         logd("isSatellitePlmn: return false");
588         return false;
589     }
590 
591     /** Get mccmnc string from NetworkRegistrationInfo. */
592     @Nullable
getMccMnc(@onNull NetworkRegistrationInfo nri)593     public static String getMccMnc(@NonNull NetworkRegistrationInfo nri) {
594         CellIdentity cellIdentity = nri.getCellIdentity();
595         if (cellIdentity == null) {
596             logd("getMccMnc: cellIdentity is null");
597             return null;
598         }
599 
600         String mcc = cellIdentity.getMccString();
601         String mnc = cellIdentity.getMncString();
602         if (mcc == null || mnc == null) {
603             logd("getMccMnc: mcc or mnc is null. mcc=" + mcc + " mnc=" + mnc);
604             return null;
605         }
606 
607         return mcc + mnc;
608     }
609 
610     @NonNull
611     private static android.telephony.satellite.stub
convertSystemSelectionSpecifierToHALFormat( @onNull SystemSelectionSpecifier systemSelectionSpecifier)612             .SystemSelectionSpecifier convertSystemSelectionSpecifierToHALFormat(
613             @NonNull SystemSelectionSpecifier systemSelectionSpecifier) {
614         android.telephony.satellite.stub.SystemSelectionSpecifier convertedSpecifier =
615                 new android.telephony.satellite.stub.SystemSelectionSpecifier();
616 
617         convertedSpecifier.mMccMnc = systemSelectionSpecifier.getMccMnc();
618         convertedSpecifier.mBands = systemSelectionSpecifier.getBands();
619         convertedSpecifier.mEarfcs = systemSelectionSpecifier.getEarfcns();
620         SatelliteInfo[] satelliteInfos = systemSelectionSpecifier.getSatelliteInfos()
621                 .toArray(new SatelliteInfo[0]);
622         android.telephony.satellite.stub.SatelliteInfo[] halSatelliteInfos =
623                 new android.telephony.satellite.stub.SatelliteInfo[satelliteInfos.length];
624         for (int i = 0; i < satelliteInfos.length; i++) {
625             halSatelliteInfos[i] = new android.telephony.satellite.stub.SatelliteInfo();
626 
627             halSatelliteInfos[i].id = new android.telephony.satellite.stub.UUID();
628             halSatelliteInfos[i].id.mostSigBits =
629                     satelliteInfos[i].getSatelliteId().getMostSignificantBits();
630             halSatelliteInfos[i].id.leastSigBits =
631                     satelliteInfos[i].getSatelliteId().getLeastSignificantBits();
632 
633             halSatelliteInfos[i].position =
634                     new android.telephony.satellite.stub.SatellitePosition();
635             halSatelliteInfos[i].position.longitudeDegree =
636                     satelliteInfos[i].getSatellitePosition().getLongitudeDegrees();
637             halSatelliteInfos[i].position.altitudeKm =
638                     satelliteInfos[i].getSatellitePosition().getAltitudeKm();
639 
640             halSatelliteInfos[i].bands = satelliteInfos[i].getBands().stream().mapToInt(
641                     Integer::intValue).toArray();
642 
643             List<EarfcnRange> earfcnRangeList = satelliteInfos[i].getEarfcnRanges();
644             halSatelliteInfos[i].earfcnRanges =
645                     new android.telephony.satellite.stub.EarfcnRange[earfcnRangeList.size()];
646             for (int j = 0; j < earfcnRangeList.size(); j++) {
647                 halSatelliteInfos[i].earfcnRanges[j] =
648                         new android.telephony.satellite.stub.EarfcnRange();
649                 halSatelliteInfos[i].earfcnRanges[j].startEarfcn = earfcnRangeList.get(
650                         j).getStartEarfcn();
651                 halSatelliteInfos[i].earfcnRanges[j].endEarfcn = earfcnRangeList.get(
652                         j).getEndEarfcn();
653             }
654         }
655         convertedSpecifier.satelliteInfos = halSatelliteInfos;
656         convertedSpecifier.tagIds = systemSelectionSpecifier.getTagIds();
657         return convertedSpecifier;
658     }
659 
660     /**
661      * Convert SystemSelectionSpecifier from framework definition to service definition
662      * @param systemSelectionSpecifier The SystemSelectionSpecifier from the framework.
663      * @return The converted SystemSelectionSpecifier for the satellite service.
664      */
665     @NonNull
666     public static List<android.telephony.satellite.stub
toSystemSelectionSpecifier( @onNull List<SystemSelectionSpecifier> systemSelectionSpecifier)667             .SystemSelectionSpecifier> toSystemSelectionSpecifier(
668             @NonNull List<SystemSelectionSpecifier> systemSelectionSpecifier) {
669         return systemSelectionSpecifier.stream().map(
670                 SatelliteServiceUtils::convertSystemSelectionSpecifierToHALFormat).collect(
671                 Collectors.toList());
672     }
673 
674     /**
675      * Expected format of the input dictionary bundle is:
676      * <ul>
677      *     <li>Key: Regional satellite config Id string.</li>
678      *     <li>Value: Integer arrays of earfcns in the corresponding regions."</li>
679      * </ul>
680      * @return The map of earfcns with key: regional satellite config Id,
681      * value: set of earfcns in the corresponding regions.
682      */
683     @NonNull
parseRegionalSatelliteEarfcns( @ullable PersistableBundle earfcnsBundle)684     public static Map<String, Set<Integer>> parseRegionalSatelliteEarfcns(
685             @Nullable PersistableBundle earfcnsBundle) {
686         Map<String, Set<Integer>> earfcnsMap = new HashMap<>();
687         if (earfcnsBundle == null || earfcnsBundle.isEmpty()) {
688             logd("parseRegionalSatelliteEarfcns: earfcnsBundle is null or empty");
689             return earfcnsMap;
690         }
691 
692         for (String configId : earfcnsBundle.keySet()) {
693             Set<Integer> earfcnsSet = new HashSet<>();
694             for (int earfcn : earfcnsBundle.getIntArray(configId)) {
695                 earfcnsSet.add(earfcn);
696             }
697             logd("parseRegionalSatelliteEarfcns: configId = " + configId + ", earfcns ="
698                     + earfcnsSet.stream().map(String::valueOf).collect(joining(",")));
699             earfcnsMap.put(configId, earfcnsSet);
700         }
701         return earfcnsMap;
702     }
703 
704     /**
705      * Returns a persistent logger to persist important log because logcat logs may not be
706      * retained long enough.
707      *
708      * @return a PersistentLogger, return {@code null} if it is not supported or encounters
709      * exception.
710      */
711     @Nullable
getPersistentLogger(@onNull Context context)712     public static PersistentLogger getPersistentLogger(@NonNull Context context) {
713         try {
714             if (context.getResources().getBoolean(
715                     R.bool.config_dropboxmanager_persistent_logging_enabled)) {
716                 return new PersistentLogger(DropBoxManagerLoggerBackend.getInstance(context));
717             }
718         } catch (RuntimeException ex) {
719             loge("getPersistentLogger: RuntimeException ex=" + ex);
720         }
721         return null;
722     }
723 
724     /** Determines whether the subscription is in carrier roaming NB-IoT NTN or not. */
isNbIotNtn(int subId)725     public static boolean isNbIotNtn(int subId) {
726         Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
727         if (phone == null) {
728             logd("isNbIotNtn(): phone is null");
729             return false;
730         }
731 
732         SatelliteController satelliteController = SatelliteController.getInstance();
733         if (satelliteController == null) {
734             logd("isNbIotNtn(): satelliteController is null");
735             return false;
736         }
737 
738         return satelliteController.isInCarrierRoamingNbIotNtn(phone);
739     }
740 
logd(@onNull String log)741     private static void logd(@NonNull String log) {
742         Log.d(TAG, log);
743     }
744 
loge(@onNull String log)745     private static void loge(@NonNull String log) {
746         Log.e(TAG, log);
747     }
748 
logv(@onNull String log)749     private static void logv(@NonNull String log) {
750         Log.v(TAG, log);
751     }
752 }
753