diff options
10 files changed, 1408 insertions, 70 deletions
diff --git a/proto/src/telephony_config_update.proto b/proto/src/telephony_config_update.proto new file mode 100644 index 0000000000..c193f3527a --- /dev/null +++ b/proto/src/telephony_config_update.proto @@ -0,0 +1,46 @@ + +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; +package com.android.internal.telephony.satellite; + +option java_package = "com.android.internal.telephony.satellite"; +option java_outer_classname = "SatelliteConfigData"; + +message TelephonyConfigProto { + optional SatelliteConfigProto satellite = 1; +} + +message SatelliteConfigProto { + optional int32 version = 1; + repeated CarrierSupportedSatelliteServicesProto carrier_supported_satellite_services = 2; + optional SatelliteRegionProto device_satellite_region = 3; +} + +message CarrierSupportedSatelliteServicesProto { + optional int32 carrier_id = 1; + repeated SatelliteProviderCapabilityProto supported_satellite_provider_capabilities = 2; +} + +message SatelliteProviderCapabilityProto{ + optional string carrier_plmn = 1; + repeated int32 allowed_services = 2; +} + +message SatelliteRegionProto { + optional bytes s2_cell_file = 1; + repeated string country_codes = 2; + optional bool is_allowed = 3; +}
\ No newline at end of file diff --git a/src/java/com/android/internal/telephony/configupdate/ConfigParser.java b/src/java/com/android/internal/telephony/configupdate/ConfigParser.java new file mode 100644 index 0000000000..5c3ac866df --- /dev/null +++ b/src/java/com/android/internal/telephony/configupdate/ConfigParser.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.configupdate; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public abstract class ConfigParser<T> { + + public static final int VERSION_UNKNOWN = -1; + + protected int mVersion = VERSION_UNKNOWN; + + protected T mConfig; + + /** + * Constructs a parser from the raw data + * + * @param data the config data + */ + public ConfigParser(@Nullable byte[] data) { + parseData(data); + } + + /** + * Constructs a parser from the input stream + * + * @param input the input stream of the config + * @return the instance of the ConfigParser + */ + public ConfigParser(@NonNull InputStream input) throws IOException { + parseData(input.readAllBytes()); + } + + /** + * Constructs a parser from the input stream + * + * @param file the input file of the config + * @return the instance of the ConfigParser + */ + public ConfigParser(@NonNull File file) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + parseData(fis.readAllBytes()); + } + } + + /** + * Get the version of the config + * + * @return the version of the config if it is defined, or VERSION_UNKNOWN + */ + public int getVersion() { + return mVersion; + } + + /** + * Get the config + * + * @return the config + */ + public @Nullable T getConfig() { + return mConfig; + } + + /** + * Get the sub config raw data by id + * + * @param id the identifier of the sub config + * @return the raw data of the sub config + */ + public @Nullable byte[] getData(String id) { + return null; + } + + /** + * Parse the config data + * + * @param data the config data + */ + protected abstract void parseData(@Nullable byte[] data); +} diff --git a/src/java/com/android/internal/telephony/configupdate/ConfigProviderAdaptor.java b/src/java/com/android/internal/telephony/configupdate/ConfigProviderAdaptor.java new file mode 100644 index 0000000000..1344534daf --- /dev/null +++ b/src/java/com/android/internal/telephony/configupdate/ConfigProviderAdaptor.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.internal.telephony.configupdate; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.concurrent.Executor; + +public interface ConfigProviderAdaptor { + + String DOMAIN_SATELLITE = "satellite"; + + /** + * Get the config from the provider + */ + @Nullable ConfigParser getConfigParser(String domain); + + class Callback { + /** + * The callback when the config is changed + * @param config the config is changed + */ + public void onChanged(@Nullable ConfigParser config) {} + } + + /** + * Register the callback to monitor the config change + * @param executor The executor to execute the callback. + * @param callback the callback to monitor the config change + */ + void registerCallback(@NonNull Executor executor, @NonNull Callback callback); + + /** + * Unregister the callback + * @param callback the callback to be unregistered + */ + void unregisterCallback(@NonNull Callback callback); +} diff --git a/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java new file mode 100644 index 0000000000..0db7844c8d --- /dev/null +++ b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.configupdate; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.satellite.SatelliteConfigParser; +import com.android.server.updates.ConfigUpdateInstallReceiver; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +public class TelephonyConfigUpdateInstallReceiver extends ConfigUpdateInstallReceiver implements + ConfigProviderAdaptor { + + private static final String TAG = "TelephonyConfigUpdateInstallReceiver"; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected static final String UPDATE_DIR = "/data/misc/telephonyconfig"; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected static final String UPDATE_CONTENT_PATH = "telephony_config.pb"; + protected static final String UPDATE_METADATA_PATH = "metadata/"; + public static final String VERSION = "version"; + + private ConcurrentHashMap<Executor, Callback> mCallbackHashMap = new ConcurrentHashMap<>(); + @NonNull + private final Object mConfigParserLock = new Object(); + @GuardedBy("mConfigParserLock") + private ConfigParser mConfigParser; + + + public static TelephonyConfigUpdateInstallReceiver sReceiverAdaptorInstance = + new TelephonyConfigUpdateInstallReceiver(); + + /** + * @return The singleton instance of TelephonyConfigUpdateInstallReceiver + */ + @NonNull + public static TelephonyConfigUpdateInstallReceiver getInstance() { + return sReceiverAdaptorInstance; + } + + public TelephonyConfigUpdateInstallReceiver() { + super(UPDATE_DIR, UPDATE_CONTENT_PATH, UPDATE_METADATA_PATH, VERSION); + } + + /** + * @return byte array type of config data protobuffer file + */ + @Nullable + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public byte[] getCurrentContent() { + try { + return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath()); + } catch (IOException e) { + Slog.i(TAG, "Failed to read current content, assuming first update!"); + return null; + } + } + + @Override + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + public void postInstall(Context context, Intent intent) { + Log.d(TAG, "Telephony config is updated in file partition"); + ConfigParser updatedConfigParser = getNewConfigParser(DOMAIN_SATELLITE, + getCurrentContent()); + + if (updatedConfigParser == null) { + Log.d(TAG, "updatedConfigParser is null"); + return; + } + + boolean isParserChanged = false; + + synchronized (getInstance().mConfigParserLock) { + if (getInstance().mConfigParser == null) { + getInstance().mConfigParser = updatedConfigParser; + isParserChanged = true; + } else { + int updatedVersion = updatedConfigParser.mVersion; + int previousVersion = getInstance().mConfigParser.mVersion; + Log.d(TAG, "previous version is " + previousVersion + " | updated version is " + + updatedVersion); + if (updatedVersion > previousVersion) { + getInstance().mConfigParser = updatedConfigParser; + isParserChanged = true; + } + } + } + + if (isParserChanged) { + if (getInstance().mCallbackHashMap.keySet().isEmpty()) { + Log.d(TAG, "mCallbackHashMap.keySet().isEmpty"); + return; + } + Iterator<Executor> iterator = + getInstance().mCallbackHashMap.keySet().iterator(); + while (iterator.hasNext()) { + Executor executor = iterator.next(); + getInstance().mCallbackHashMap.get(executor).onChanged( + updatedConfigParser); + } + } + } + + @Nullable + @Override + public ConfigParser getConfigParser(String domain) { + Log.d(TAG, "getConfigParser"); + synchronized (getInstance().mConfigParserLock) { + if (getInstance().mConfigParser == null) { + Log.d(TAG, "CreateNewConfigParser with domain " + domain); + getInstance().mConfigParser = getNewConfigParser(domain, getCurrentContent()); + } + return getInstance().mConfigParser; + } + } + + @Override + public void registerCallback(@NonNull Executor executor, @NonNull Callback callback) { + mCallbackHashMap.put(executor, callback); + } + + @Override + public void unregisterCallback(@NonNull Callback callback) { + Iterator<Executor> iterator = mCallbackHashMap.keySet().iterator(); + while (iterator.hasNext()) { + Executor executor = iterator.next(); + if (mCallbackHashMap.get(executor) == callback) { + mCallbackHashMap.remove(executor); + break; + } + } + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public File getUpdateDir() { + return getInstance().updateDir; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public File getUpdateContent() { + return getInstance().updateContent; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public ConcurrentHashMap<Executor, Callback> getCallbackMap() { + return getInstance().mCallbackHashMap; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public void setCallbackMap(ConcurrentHashMap<Executor, Callback> map) { + getInstance().mCallbackHashMap = map; + } + + /** + * @param data byte array type of config data + * @return when data is null, return null otherwise return ConfigParser + */ + @Nullable + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public ConfigParser getNewConfigParser(String domain, @Nullable byte[] data) { + if (data == null) { + Log.d(TAG, "content data is null"); + return null; + } + switch (domain) { + case DOMAIN_SATELLITE: + return new SatelliteConfigParser(data); + default: + Log.e(TAG, "DOMAIN should be specified"); + return null; + } + } +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java b/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java new file mode 100644 index 0000000000..8d7e723184 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.satellite; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.satellite.nano.SatelliteConfigData; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * SatelliteConfig is utility class for satellite. + * It is obtained through the getConfig() at the SatelliteConfigParser. + */ +public class SatelliteConfig { + + private static final String TAG = "SatelliteConfig"; + private static final String SATELLITE_DIR_NAME = "satellite"; + private static final String S2_CELL_FILE_NAME = "s2_cell_file"; + private int mVersion; + private Map<Integer, Map<String, Set<Integer>>> mSupportedServicesPerCarrier; + private List<String> mSatelliteRegionCountryCodes; + private Boolean mIsSatelliteRegionAllowed; + private Path mSatS2FilePath; + private SatelliteConfigData.SatelliteConfigProto mConfigData; + + public SatelliteConfig(SatelliteConfigData.SatelliteConfigProto configData) { + mConfigData = configData; + mVersion = mConfigData.version; + mSupportedServicesPerCarrier = getCarrierSupportedSatelliteServices(); + mSatelliteRegionCountryCodes = List.of( + mConfigData.deviceSatelliteRegion.countryCodes); + mIsSatelliteRegionAllowed = mConfigData.deviceSatelliteRegion.isAllowed; + mSatS2FilePath = null; + + Log.d(TAG, "mVersion:" + mVersion + " | " + + "mSupportedServicesPerCarrier:" + mSupportedServicesPerCarrier + " | " + + "mSatelliteRegionCountryCodes:" + mSatelliteRegionCountryCodes + " | " + + "mIsSatelliteRegionAllowed:" + mIsSatelliteRegionAllowed + " | " + + "s2CellFile size:" + mConfigData.deviceSatelliteRegion.s2CellFile.length); + } + + /** + * @return a Map data with carrier_id, plmns and allowed_services. + */ + private Map<Integer, Map<String, Set<Integer>>> getCarrierSupportedSatelliteServices() { + SatelliteConfigData.CarrierSupportedSatelliteServicesProto[] satelliteServices = + mConfigData.carrierSupportedSatelliteServices; + Map<Integer, Map<String, Set<Integer>>> carrierToServicesMap = new HashMap<>(); + for (SatelliteConfigData.CarrierSupportedSatelliteServicesProto carrierProto : + satelliteServices) { + SatelliteConfigData.SatelliteProviderCapabilityProto[] satelliteCapabilities = + carrierProto.supportedSatelliteProviderCapabilities; + Map<String, Set<Integer>> satelliteCapabilityMap = new HashMap<>(); + for (SatelliteConfigData.SatelliteProviderCapabilityProto capabilityProto : + satelliteCapabilities) { + String carrierPlmn = capabilityProto.carrierPlmn; + Set<Integer> allowedServices = new HashSet<>(); + for (int service : capabilityProto.allowedServices) { + allowedServices.add(service); + } + satelliteCapabilityMap.put(carrierPlmn, allowedServices); + } + carrierToServicesMap.put(carrierProto.carrierId, satelliteCapabilityMap); + } + return carrierToServicesMap; + } + + /** + * Get satellite plmns for carrier + * + * @param carrierId the carrier identifier. + * @return Plmns corresponding to carrier identifier. + */ + @NonNull + public List<String> getAllSatellitePlmnsForCarrier(int carrierId) { + if (mSupportedServicesPerCarrier != null) { + Map<String, Set<Integer>> satelliteCapabilitiesMap = mSupportedServicesPerCarrier.get( + carrierId); + if (satelliteCapabilitiesMap != null) { + return new ArrayList<>(satelliteCapabilitiesMap.keySet()); + } + } + Log.d(TAG, "getAllSatellitePlmnsForCarrier : mConfigData is null or no config data"); + return new ArrayList<>(); + } + + /** + * Get supported satellite services of all providers for a carrier. + * The format of the return value - Key: PLMN, Value: Set of supported satellite services. + * + * @param carrierId the carrier identifier. + * @return all supported satellite services for a carrier + */ + @NonNull + public Map<String, Set<Integer>> getSupportedSatelliteServices(int carrierId) { + if (mSupportedServicesPerCarrier != null) { + Map<String, Set<Integer>> satelliteCapaMap = + mSupportedServicesPerCarrier.get(carrierId); + if (satelliteCapaMap != null) { + return satelliteCapaMap; + } else { + Log.d(TAG, "No supported services found for carrier=" + carrierId); + } + } else { + Log.d(TAG, "mSupportedServicesPerCarrier is null"); + } + return new HashMap<>(); + } + + /** + * @return satellite region country codes + */ + @NonNull + public List<String> getDeviceSatelliteCountryCodes() { + if (mSatelliteRegionCountryCodes != null) { + return mSatelliteRegionCountryCodes; + } + Log.d(TAG, "getDeviceSatelliteCountryCodes : mConfigData is null or no config data"); + return new ArrayList<>(); + } + + /** + * @return satellite access allow value, if there is no config data then it returns null. + */ + @Nullable + public Boolean isSatelliteDataForAllowedRegion() { + if (mIsSatelliteRegionAllowed == null) { + Log.d(TAG, "getIsSatelliteRegionAllowed : mConfigData is null or no config data"); + } + return mIsSatelliteRegionAllowed; + } + + + /** + * @param context the Context + * @return satellite s2_cell_file path + */ + @Nullable + public Path getSatelliteS2CellFile(@Nullable Context context) { + if (context == null) { + Log.d(TAG, "getSatelliteS2CellFile : context is null"); + return null; + } + + if (isFileExist(mSatS2FilePath)) { + Log.d(TAG, "File mSatS2FilePath is already exist"); + return mSatS2FilePath; + } + + if (mConfigData != null && mConfigData.deviceSatelliteRegion != null) { + mSatS2FilePath = copySatS2FileToPhoneDirectory(context, + mConfigData.deviceSatelliteRegion.s2CellFile); + return mSatS2FilePath; + } + Log.d(TAG, "getSatelliteS2CellFile :" + + "mConfigData is null or mConfigData.deviceSatelliteRegion is null"); + return null; + } + + /** + * @param context the Context + * @param byteArrayFile byte array type of protobuffer config data + * @return the satellite_cell_file path + */ + @Nullable + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public Path copySatS2FileToPhoneDirectory(@Nullable Context context, + @Nullable byte[] byteArrayFile) { + + if (context == null || byteArrayFile == null) { + Log.d(TAG, "copySatS2FileToPhoneDirectory : context or byteArrayFile are null"); + return null; + } + + File satS2FileDir = context.getDir(SATELLITE_DIR_NAME, Context.MODE_PRIVATE); + if (!satS2FileDir.exists()) { + satS2FileDir.mkdirs(); + } + + Path targetSatS2FilePath = satS2FileDir.toPath().resolve(S2_CELL_FILE_NAME); + try { + InputStream inputStream = new ByteArrayInputStream(byteArrayFile); + if (inputStream == null) { + Log.d(TAG, "copySatS2FileToPhoneDirectory: Resource=" + S2_CELL_FILE_NAME + + " not found"); + } else { + Files.copy(inputStream, targetSatS2FilePath, StandardCopyOption.REPLACE_EXISTING); + } + } catch (IOException ex) { + Log.e(TAG, "copySatS2FileToPhoneDirectory: ex=" + ex); + } + return targetSatS2FilePath; + } + + /** + * @return {@code true} if the SatS2File is already existed and {@code false} otherwise. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public boolean isFileExist(Path filePath) { + return Files.exists(filePath); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConfigParser.java b/src/java/com/android/internal/telephony/satellite/SatelliteConfigParser.java new file mode 100644 index 0000000000..4ff1880ba5 --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/SatelliteConfigParser.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.satellite; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; + +import com.android.internal.telephony.configupdate.ConfigParser; +import com.android.internal.telephony.satellite.nano.SatelliteConfigData; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + + +/** + * SatelliteConfigParser parses the config data and create SatelliteConfig. + * The config data is located at "/data/misc/telephonyconfig/telephony_config.pb". + * It is obtained through the getConfigParser() at the TelephonyConfigUpdateInstallReceiver. + */ +public class SatelliteConfigParser extends ConfigParser<SatelliteConfig> { + private static final String TAG = "SatelliteConfigParser"; + + /** + * Create an instance of SatelliteConfigParser with byte array data. + * + * @param data the config data formatted as byte array. + */ + public SatelliteConfigParser(@Nullable byte[] data) { + super(data); + } + + /** + * Create an instance of SatelliteConfigParser with InputStream data. + * + * @param input the config data formatted as InputStream. + */ + public SatelliteConfigParser(@NonNull InputStream input) + throws IOException { + super(input); + } + + /** + * Create an instance of SatelliteConfigParser with File data. + * + * @param file the config data formatted as File. + */ + public SatelliteConfigParser(@NonNull File file) throws IOException { + super(file); + } + + @Override + protected void parseData(@Nullable byte[] data) { + boolean parseError = false; + try { + if (data == null) { + Log.d(TAG, "config data is null"); + return; + } + SatelliteConfigData.TelephonyConfigProto telephonyConfigData = + SatelliteConfigData.TelephonyConfigProto.parseFrom(data); + if (telephonyConfigData == null || telephonyConfigData.satellite == null) { + Log.e(TAG, "telephonyConfigData or telephonyConfigData.satellite is null"); + return; + } + mVersion = telephonyConfigData.satellite.version; + mConfig = new SatelliteConfig(telephonyConfigData.satellite); + Log.d(TAG, "SatelliteConfig is created"); + } catch (Exception e) { + parseError = true; + Log.e(TAG, "Parse Error : " + e.getMessage()); + } finally { + if (parseError) { + mVersion = VERSION_UNKNOWN; + mConfig = null; + } + } + } +} diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java index 0d3973d127..55db6d21b5 100644 --- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java +++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java @@ -34,6 +34,8 @@ import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODE import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED; import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS; +import static com.android.internal.telephony.configupdate.ConfigProviderAdaptor.DOMAIN_SATELLITE; + import android.annotation.ArrayRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -66,6 +68,8 @@ import android.os.ICancellationSignal; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; +import android.os.Registrant; +import android.os.RegistrantList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceSpecificException; @@ -101,6 +105,9 @@ import com.android.internal.telephony.DeviceStateMonitor; import com.android.internal.telephony.IIntegerConsumer; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.configupdate.ConfigParser; +import com.android.internal.telephony.configupdate.ConfigProviderAdaptor; +import com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver; import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats; @@ -118,6 +125,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -187,6 +195,7 @@ public class SatelliteController extends Handler { private static final int EVENT_SERVICE_STATE_CHANGED = 37; private static final int EVENT_SATELLITE_CAPABILITIES_CHANGED = 38; private static final int EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT = 39; + private static final int EVENT_SATELLITE_CONFIG_DATA_UPDATED = 40; @NonNull private static SatelliteController sInstance; @NonNull private final Context mContext; @@ -298,6 +307,7 @@ public class SatelliteController extends Handler { @NonNull private final CarrierConfigManager mCarrierConfigManager; @NonNull private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener; + @NonNull private final ConfigProviderAdaptor.Callback mConfigDataUpdatedCallback; @NonNull private final Object mCarrierConfigArrayLock = new Object(); @GuardedBy("mCarrierConfigArrayLock") @NonNull private final SparseArray<PersistableBundle> mCarrierConfigArray = new SparseArray<>(); @@ -365,6 +375,8 @@ public class SatelliteController extends Handler { private static final String NOTIFICATION_CHANNEL = "satelliteChannel"; private static final String NOTIFICATION_CHANNEL_ID = "satellite"; + private final RegistrantList mSatelliteConfigUpdateChangedRegistrants = new RegistrantList(); + /** * @return The singleton instance of SatelliteController. */ @@ -454,12 +466,64 @@ public class SatelliteController extends Handler { handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId); mCarrierConfigManager.registerCarrierConfigChangeListener( new HandlerExecutor(new Handler(looper)), mCarrierConfigChangeListener); + + mConfigDataUpdatedCallback = new ConfigProviderAdaptor.Callback() { + @Override + public void onChanged(@Nullable ConfigParser config) { + SatelliteControllerHandlerRequest request = + new SatelliteControllerHandlerRequest(true, + SatelliteServiceUtils.getPhone()); + sendRequestAsync(EVENT_SATELLITE_CONFIG_DATA_UPDATED, request, null); + } + }; + TelephonyConfigUpdateInstallReceiver.getInstance() + .registerCallback(Executors.newSingleThreadExecutor(), mConfigDataUpdatedCallback); + mDSM.registerForSignalStrengthReportDecision(this, CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING, null); loadSatelliteSharedPreferences(); mWaitTimeForSatelliteEnablingResponse = getWaitForSatelliteEnablingResponseTimeoutMillis(); } + /** + * Register a callback to get a updated satellite config data. + * @param h Handler to notify + * @param what msg.what when the message is delivered + * @param obj AsyncResult.userObj when the message is delivered + */ + public void registerForConfigUpdateChanged(Handler h, int what, Object obj) { + Registrant r = new Registrant(h, what, obj); + mSatelliteConfigUpdateChangedRegistrants.add(r); + } + + /** + * Unregister a callback to get a updated satellite config data. + * @param h Handler to notify + */ + public void unregisterForConfigUpdateChanged(Handler h) { + mSatelliteConfigUpdateChangedRegistrants.remove(h); + } + + /** + * Get satelliteConfig from SatelliteConfigParser + */ + public SatelliteConfig getSatelliteConfig() { + if (getSatelliteConfigParser() == null) { + Log.d(TAG, "getSatelliteConfigParser() is not ready"); + return null; + } + return (SatelliteConfig) getSatelliteConfigParser().getConfig(); + } + + /** + * Get SatelliteConfigParser from TelephonyConfigUpdateInstallReceiver + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public SatelliteConfigParser getSatelliteConfigParser() { + return (SatelliteConfigParser) TelephonyConfigUpdateInstallReceiver + .getInstance().getConfigParser(DOMAIN_SATELLITE); + } + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) protected void initializeSatelliteModeRadios() { if (mContentResolver != null) { @@ -1287,6 +1351,12 @@ public class SatelliteController extends Handler { break; } + case EVENT_SATELLITE_CONFIG_DATA_UPDATED: { + handleEventConfigDataUpdated(); + mSatelliteConfigUpdateChangedRegistrants.notifyRegistrants(); + break; + } + default: Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " + msg.what); @@ -1294,6 +1364,19 @@ public class SatelliteController extends Handler { } } + private void handleEventConfigDataUpdated() { + updateSupportedSatelliteServicesForActiveSubscriptions(); + int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(true); + if (activeSubIds != null) { + for (int subId : activeSubIds) { + processNewCarrierConfigData(subId); + } + } else { + loge("updateSupportedSatelliteServicesForActiveSubscriptions: " + + "activeSubIds is null"); + } + } + private void notifyRequester(SatelliteControllerHandlerRequest request) { synchronized (request) { request.notifyAll(); @@ -3193,9 +3276,22 @@ public class SatelliteController extends Handler { return; } + SatelliteConfig satelliteConfig = getSatelliteConfig(); + if (satelliteConfig != null) { + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + int carrierId = tm.createForSubscriptionId(subId).getSimCarrierId(); + List<String> plmnList = satelliteConfig.getAllSatellitePlmnsForCarrier(carrierId); + if (!plmnList.isEmpty()) { + logd("mMergedPlmnListPerCarrier is updated by ConfigUpdater : " + plmnList); + mMergedPlmnListPerCarrier.put(subId, plmnList); + return; + } + } + if (mSatelliteServicesSupportedByCarriers.containsKey(subId)) { carrierPlmnList = mSatelliteServicesSupportedByCarriers.get(subId).keySet().stream().toList(); + logd("mMergedPlmnListPerCarrier is updated by carrier config"); } else { carrierPlmnList = new ArrayList<>(); } @@ -3205,10 +3301,31 @@ public class SatelliteController extends Handler { } private void updateSupportedSatelliteServices(int subId) { + logd("updateSupportedSatelliteServices with subId " + subId); synchronized (mSupportedSatelliteServicesLock) { + SatelliteConfig satelliteConfig = getSatelliteConfig(); + + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + int carrierId = tm.createForSubscriptionId(subId).getSimCarrierId(); + + if (satelliteConfig != null) { + Map<String, Set<Integer>> supportedServicesPerPlmn = + satelliteConfig.getSupportedSatelliteServices(carrierId); + if (!supportedServicesPerPlmn.isEmpty()) { + mSatelliteServicesSupportedByCarriers.put(subId, supportedServicesPerPlmn); + logd("updateSupportedSatelliteServices using ConfigUpdater, " + + "supportedServicesPerPlmn = " + supportedServicesPerPlmn); + updatePlmnListPerCarrier(subId); + return; + } else { + logd("supportedServicesPerPlmn is empty"); + } + } + mSatelliteServicesSupportedByCarriers.put( subId, readSupportedSatelliteServicesFromCarrierConfig(subId)); updatePlmnListPerCarrier(subId); + logd("updateSupportedSatelliteServices using carrier config"); } } @@ -3262,8 +3379,11 @@ public class SatelliteController extends Handler { updateCarrierConfig(subId); updateEntitlementPlmnListPerCarrier(subId); updateSupportedSatelliteServicesForActiveSubscriptions(); - configureSatellitePlmnForCarrier(subId); + processNewCarrierConfigData(subId); + } + private void processNewCarrierConfigData(int subId) { + configureSatellitePlmnForCarrier(subId); synchronized (mIsSatelliteEnabledLock) { mSatelliteAttachRestrictionForCarrierArray.clear(); mIsSatelliteAttachEnabledForCarrierArrayPerSub.clear(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiverTest.java new file mode 100644 index 0000000000..629327d243 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiverTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.configupdate; + +import static com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver.UPDATE_CONTENT_PATH; +import static com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver.UPDATE_DIR; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Intent; + +import androidx.annotation.Nullable; + +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.satellite.SatelliteConfigParser; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.util.Base64; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class TelephonyConfigUpdateInstallReceiverTest extends TelephonyTest { + + public static final String DOMAIN_SATELLITE = "satellite"; + @Mock + private Executor mExecutor; + @Mock + private ConfigProviderAdaptor.Callback mCallback; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + logd(TAG + " Setup!"); + } + + @After + public void tearDown() throws Exception { + logd(TAG + " tearDown"); + super.tearDown(); + } + + @Test + public void testTelephonyConfigUpdateInstallReceiver() { + TelephonyConfigUpdateInstallReceiver testReceiver = + new TelephonyConfigUpdateInstallReceiver(); + assertEquals(UPDATE_DIR, testReceiver.getUpdateDir().toString()); + assertEquals(new File(new File(UPDATE_DIR), UPDATE_CONTENT_PATH).toString(), + testReceiver.getUpdateContent().toString()); + } + + @Test + public void testGetInstance() { + TelephonyConfigUpdateInstallReceiver testReceiver1 = + TelephonyConfigUpdateInstallReceiver.getInstance(); + TelephonyConfigUpdateInstallReceiver testReceiver2 = + TelephonyConfigUpdateInstallReceiver.getInstance(); + assertSame(testReceiver1, testReceiver2); + } + + @Test + public void testPostInstall() throws Exception { + // create spyTelephonyConfigUpdateInstallReceiver + TelephonyConfigUpdateInstallReceiver spyTelephonyConfigUpdateInstallReceiver = + spy(new TelephonyConfigUpdateInstallReceiver()); + + // mock BeforeParser + String mBase64StrForPBByteArray = + "CjYIBBIeCAESDgoGMzEwMTYwEAEQAhADEgoKBjMxMDIyMBADGhIKCjAxMjM0NTY3ODkSAlVTGAE="; + byte[] mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray); + doReturn(mBytesProtoBuffer).when( + spyTelephonyConfigUpdateInstallReceiver).getCurrentContent(); + SatelliteConfigParser mMockSatelliteConfigParserBefore = + spy(new SatelliteConfigParser(mBytesProtoBuffer)); + doReturn(mMockSatelliteConfigParserBefore).when( + spyTelephonyConfigUpdateInstallReceiver).getConfigParser(DOMAIN_SATELLITE); + + // mock UpdatedParser + SatelliteConfigParser spySatelliteConfigParserAfter = + spy(new SatelliteConfigParser(mBytesProtoBuffer)); + doReturn(5).when(spySatelliteConfigParserAfter).getVersion(); + doReturn(spySatelliteConfigParserAfter).when(spyTelephonyConfigUpdateInstallReceiver) + .getNewConfigParser(any(), any()); + + replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance", + null, spyTelephonyConfigUpdateInstallReceiver); + + assertSame(spyTelephonyConfigUpdateInstallReceiver, + TelephonyConfigUpdateInstallReceiver.getInstance()); + + ConcurrentHashMap<Executor, ConfigProviderAdaptor.Callback> spyCallbackHashMap = spy( + new ConcurrentHashMap<>()); + spyCallbackHashMap.put(mExecutor, mCallback); + spyTelephonyConfigUpdateInstallReceiver.setCallbackMap(spyCallbackHashMap); + spyTelephonyConfigUpdateInstallReceiver.postInstall(mContext, new Intent()); + + verify(spyCallbackHashMap, atLeast(1)).keySet(); + } + + + @Test + public void testGetConfig() throws Exception { + TelephonyConfigUpdateInstallReceiver spyTelephonyConfigUpdateInstallReceiver = + spy(new TelephonyConfigUpdateInstallReceiver()); + + doReturn(null).when( + spyTelephonyConfigUpdateInstallReceiver).getCurrentContent(); + + replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance", + null, spyTelephonyConfigUpdateInstallReceiver); + assertNull(TelephonyConfigUpdateInstallReceiver.getInstance().getConfigParser( + DOMAIN_SATELLITE)); + + String mBase64StrForPBByteArray = + "CjYIBBIeCAESDgoGMzEwMTYwEAEQAhADEgoKBjMxMDIyMBADGhIKCjAxMjM0NTY3ODkSAlVTGAE="; + byte[] mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray); + doReturn(mBytesProtoBuffer).when( + spyTelephonyConfigUpdateInstallReceiver).getCurrentContent(); + + replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance", + null, spyTelephonyConfigUpdateInstallReceiver); + + assertNotNull(TelephonyConfigUpdateInstallReceiver.getInstance().getConfigParser( + DOMAIN_SATELLITE)); + } + + @Test + public void testRegisterUnRegisterCallback() { + TelephonyConfigUpdateInstallReceiver testReceiver = + TelephonyConfigUpdateInstallReceiver.getInstance(); + + ConfigProviderAdaptor.Callback testCallback = new ConfigProviderAdaptor.Callback() { + @Override + public void onChanged(@Nullable ConfigParser config) { + super.onChanged(config); + } + }; + Executor executor = Executors.newSingleThreadExecutor(); + + testReceiver.registerCallback(executor, testCallback); + assertSame(testCallback, testReceiver.getCallbackMap().get(executor)); + + testReceiver.unregisterCallback(testCallback); + assertEquals(0, testReceiver.getCallbackMap().size()); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java new file mode 100644 index 0000000000..e4f0255a92 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.satellite; + +import static junit.framework.Assert.assertNotNull; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.testing.AndroidTestingRunner; + +import com.android.internal.telephony.TelephonyTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@RunWith(AndroidTestingRunner.class) + +public class SatelliteConfigParserTest extends TelephonyTest { + + /** + * satelliteConfigBuilder.setVersion(4); + * + * carrierSupportedSatelliteServiceBuilder.setCarrierId(1); + * + * satelliteProviderCapabilityBuilder.setCarrierPlmn("310160"); + * satelliteProviderCapabilityBuilder.addAllowedServices(1); + * satelliteProviderCapabilityBuilder.addAllowedServices(2); + * satelliteProviderCapabilityBuilder.addAllowedServices(3); + * + * satelliteProviderCapabilityBuilder.setCarrierPlmn("310220"); + * satelliteProviderCapabilityBuilder.addAllowedServices(3); + * + * String test = "0123456789"; + * bigString.append(test.repeat(1)); + * satelliteRegionBuilder.setS2CellFile(ByteString.copyFrom(bigString.toString().getBytes())); + * satelliteRegionBuilder.addCountryCodes("US"); + * satelliteRegionBuilder.setIsAllowed(true); + */ + private String mBase64StrForPBByteArray = + "CjYIBBIeCAESDgoGMzEwMTYwEAEQAhADEgoKBjMxMDIyMBADGhIKCjAxMjM0NTY3ODkSAlVTGAE="; + private byte[] mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray); + + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + logd(TAG + " Setup!"); + } + + @After + public void tearDown() throws Exception { + logd(TAG + " tearDown"); + super.tearDown(); + } + + @Test + public void testGetAllSatellitePlmnsForCarrier() { + List<String> compareList_cid1 = new ArrayList<>(); + compareList_cid1.add("310160"); + compareList_cid1.add("310220"); + List<String> compareList_cid_placeholder = new ArrayList<>(); + compareList_cid_placeholder.add("310260"); + compareList_cid_placeholder.add("45060"); + + + SatelliteConfigParser satelliteConfigParserNull = new SatelliteConfigParser((byte[]) null); + assertNotNull(satelliteConfigParserNull); + assertNull(satelliteConfigParserNull.getConfig()); + + SatelliteConfigParser satelliteConfigParserPlaceHolder = + new SatelliteConfigParser((byte[]) null); + assertNotNull(satelliteConfigParserPlaceHolder); + assertNull(satelliteConfigParserPlaceHolder.getConfig()); + + SatelliteConfigParser satelliteConfigParser = new SatelliteConfigParser(mBytesProtoBuffer); + + List<String> parsedList1 = satelliteConfigParser.getConfig() + .getAllSatellitePlmnsForCarrier(1); + Collections.sort(compareList_cid1); + Collections.sort(compareList_cid_placeholder); + Collections.sort(parsedList1); + + assertEquals(compareList_cid1, parsedList1); + assertNotEquals(compareList_cid_placeholder, parsedList1); + + List<String> parsedList2 = satelliteConfigParser.getConfig() + .getAllSatellitePlmnsForCarrier(0); + assertEquals(0, parsedList2.size()); + } + + @Test + public void testGetSupportedSatelliteServices() { + Map<String, Set<Integer>> compareMapCarrierId1 = new HashMap<>(); + Set<Integer> compareSet310160 = new HashSet<>(); + compareSet310160.add(1); + compareSet310160.add(2); + compareSet310160.add(3); + compareMapCarrierId1.put("310160", compareSet310160); + + Set<Integer> compareSet310220 = new HashSet<>(); + compareSet310220.add(3); + compareMapCarrierId1.put("310220", compareSet310220); + + SatelliteConfigParser satelliteConfigParserNull = new SatelliteConfigParser((byte[]) null); + assertNotNull(satelliteConfigParserNull); + assertNull(satelliteConfigParserNull.getConfig()); + + SatelliteConfigParser satelliteConfigParserPlaceholder = + new SatelliteConfigParser("test".getBytes()); + assertNotNull(satelliteConfigParserPlaceholder); + assertNull(satelliteConfigParserPlaceholder.getConfig()); + + SatelliteConfigParser satelliteConfigParser = new SatelliteConfigParser(mBytesProtoBuffer); + Map<String, Set<Integer>> parsedMap1 = satelliteConfigParser.getConfig() + .getSupportedSatelliteServices(0); + Map<String, Set<Integer>> parsedMap2 = satelliteConfigParser.getConfig() + .getSupportedSatelliteServices(1); + assertEquals(0, parsedMap1.size()); + assertEquals(2, parsedMap2.size()); + assertEquals(compareMapCarrierId1, parsedMap2); + } + + @Test + public void testGetDeviceSatelliteCountryCodes() { + List<String> compareList_countryCodes = new ArrayList<>(); + compareList_countryCodes.add("US"); + Collections.sort(compareList_countryCodes); + + List<String> compareList_countryCodes_placeholder = new ArrayList<>(); + compareList_countryCodes_placeholder.add("US"); + compareList_countryCodes_placeholder.add("IN"); + Collections.sort(compareList_countryCodes_placeholder); + + SatelliteConfigParser satelliteConfigParserNull = new SatelliteConfigParser((byte[]) null); + assertNotNull(satelliteConfigParserNull); + assertNull(satelliteConfigParserNull.getConfig()); + + SatelliteConfigParser satelliteConfigParser = new SatelliteConfigParser(mBytesProtoBuffer); + List<String> tempList = satelliteConfigParser.getConfig().getDeviceSatelliteCountryCodes(); + List<String> parsedList = new ArrayList<>(tempList); + Collections.sort(parsedList); + + assertEquals(compareList_countryCodes, parsedList); + assertNotEquals(compareList_countryCodes_placeholder, parsedList); + } + + @Test + public void testGetSatelliteS2CellFile() { + final String filePath = "/data/user_de/0/com.android.phone/app_satellite/s2_cell_file"; + Path targetSatS2FilePath = Paths.get(filePath); + + SatelliteConfigParser mockedSatelliteConfigParserNull = spy( + new SatelliteConfigParser((byte[]) null)); + assertNotNull(mockedSatelliteConfigParserNull); + assertNull(mockedSatelliteConfigParserNull.getConfig()); + + SatelliteConfigParser mockedSatelliteConfigParserPlaceholder = spy( + new SatelliteConfigParser("test".getBytes())); + assertNotNull(mockedSatelliteConfigParserPlaceholder); + assertNull(mockedSatelliteConfigParserPlaceholder.getConfig()); + + SatelliteConfigParser mockedSatelliteConfigParser = + spy(new SatelliteConfigParser(mBytesProtoBuffer)); + SatelliteConfig mockedSatelliteConfig = Mockito.mock(SatelliteConfig.class); + doReturn(targetSatS2FilePath).when(mockedSatelliteConfig).getSatelliteS2CellFile(any()); + doReturn(mockedSatelliteConfig).when(mockedSatelliteConfigParser).getConfig(); +// assertNotNull(mockedSatelliteConfigParser.getConfig()); +// doReturn(false).when(mockedSatelliteConfigParser).getConfig().isFileExist(any()); +// doReturn(targetSatS2FilePath).when(mockedSatelliteConfigParser).getConfig() +// .copySatS2FileToPhoneDirectory(any(), any()); + assertEquals(targetSatS2FilePath, + mockedSatelliteConfigParser.getConfig().getSatelliteS2CellFile(mContext)); + } + + @Test + public void testGetSatelliteAccessAllow() { + SatelliteConfigParser satelliteConfigParserNull = new SatelliteConfigParser((byte[]) null); + assertNotNull(satelliteConfigParserNull); + assertNull(satelliteConfigParserNull.getConfig()); + + SatelliteConfigParser satelliteConfigParserPlaceholder = + new SatelliteConfigParser("test".getBytes()); + assertNotNull(satelliteConfigParserPlaceholder); + assertNull(satelliteConfigParserPlaceholder.getConfig()); + + SatelliteConfigParser satelliteConfigParser = new SatelliteConfigParser(mBytesProtoBuffer); + assertNotNull(satelliteConfigParser); + assertNotNull(satelliteConfigParser.getConfig()); + assertTrue(satelliteConfigParser.getConfig().isSatelliteDataForAllowedRegion()); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java index 431b4cc182..f1a8de93e0 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java @@ -124,6 +124,8 @@ import com.android.internal.telephony.IVoidConsumer; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.configupdate.ConfigProviderAdaptor; +import com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver; import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats; @@ -189,6 +191,10 @@ public class SatelliteControllerTest extends TelephonyTest { @Mock private ISatelliteTransmissionUpdateCallback mStartTransmissionUpdateCallback; @Mock private ISatelliteTransmissionUpdateCallback mStopTransmissionUpdateCallback; @Mock private FeatureFlags mFeatureFlags; + @Mock private TelephonyConfigUpdateInstallReceiver mMockTelephonyConfigUpdateInstallReceiver; + @Mock private SatelliteConfigParser mMockConfigParser; + @Mock private SatelliteConfig mMockConfig; + private Semaphore mIIntegerConsumerSemaphore = new Semaphore(0); private IIntegerConsumer mIIntegerConsumer = new IIntegerConsumer.Stub() { @Override @@ -459,6 +465,8 @@ public class SatelliteControllerTest extends TelephonyTest { replaceInstance(SubscriptionManagerService.class, "sInstance", null, mMockSubscriptionManagerService); replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mPhone2}); + replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance", + null, mMockTelephonyConfigUpdateInstallReceiver); mServiceState2 = Mockito.mock(ServiceState.class); when(mPhone.getServiceState()).thenReturn(mServiceState); @@ -536,6 +544,9 @@ public class SatelliteControllerTest extends TelephonyTest { any(Handler.class), eq(28) /* EVENT_SATELLITE_MODEM_STATE_CHANGED */, eq(null)); + + doReturn(mMockConfigParser).when(mMockTelephonyConfigUpdateInstallReceiver) + .getConfigParser(ConfigProviderAdaptor.DOMAIN_SATELLITE); } @After @@ -2761,41 +2772,116 @@ public class SatelliteControllerTest extends TelephonyTest { eq(plmnListPerCarrier), eq(allSatellitePlmnList), any(Message.class)); } + private void setConfigData(List<String> plmnList) { + doReturn(plmnList).when(mMockConfig).getAllSatellitePlmnsForCarrier(anyInt()); + doReturn(mMockConfig).when(mMockConfigParser).getConfig(); + + Map<String, List<Integer>> servicePerPlmn = new HashMap<>(); + List<List<Integer>> serviceLists = Arrays.asList( + Arrays.asList(1), + Arrays.asList(3), + Arrays.asList(5) + ); + for (int i = 0; i < plmnList.size(); i++) { + servicePerPlmn.put(plmnList.get(i), serviceLists.get(i)); + } + doReturn(servicePerPlmn).when(mMockConfig).getSupportedSatelliteServices(anyInt()); + doReturn(mMockConfig).when(mMockConfigParser).getConfig(); + } + @Test - public void testUpdatePlmnListPerCarrier() throws Exception { - logd("testUpdatePlmnListPerCarrier"); + public void testUpdateSupportedSatelliteServices() throws Exception { + logd("testUpdateSupportedSatelliteServices"); when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true); replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier", mSatelliteControllerUT, new SparseArray<>()); replaceInstance(SatelliteController.class, "mSatelliteServicesSupportedByCarriers", mSatelliteControllerUT, new HashMap<>()); + List<Integer> servicesPerPlmn; + + // verify whether an empty list is returned with conditions below + // the config data plmn list : empty + // the carrier config plmn list : empty + setConfigData(new ArrayList<>()); + setCarrierConfigDataPlmnList(new ArrayList<>()); + invokeCarrierConfigChanged(); + servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "31016"); + assertEquals(new ArrayList<>(), servicesPerPlmn); + + // Verify whether the carrier config plmn list is returned with conditions below + // the config data plmn list : empty + // the carrier config plmn list : exist with services {{2}, {1, 3}, {2}} + setConfigData(new ArrayList<>()); + setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104")); + invokeCarrierConfigChanged(); + servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101"); + assertEquals(Arrays.asList(2).stream().sorted().toList(), + servicesPerPlmn.stream().sorted().toList()); + servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00102"); + assertEquals(Arrays.asList(1, 3).stream().sorted().toList(), + servicesPerPlmn.stream().sorted().toList()); + servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00104"); + assertEquals(Arrays.asList(2).stream().sorted().toList(), + servicesPerPlmn.stream().sorted().toList()); + + // Verify whether the carrier config plmn list is returned with conditions below + // the config data plmn list : exist with services {{1}, {3}, {5}} + // the carrier config plmn list : exist with services {{2}, {1, 3}, {2}} + setConfigData(Arrays.asList("00101", "00102", "31024")); + setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104")); + invokeCarrierConfigChanged(); + servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101"); + assertEquals(Arrays.asList(1).stream().sorted().toList(), + servicesPerPlmn.stream().sorted().toList()); + servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00102"); + assertEquals(Arrays.asList(3).stream().sorted().toList(), + servicesPerPlmn.stream().sorted().toList()); + servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00104"); + assertEquals(new ArrayList<>(), servicesPerPlmn.stream().sorted().toList()); + servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "31024"); + assertEquals(Arrays.asList(5).stream().sorted().toList(), + servicesPerPlmn.stream().sorted().toList()); + } + private void setEntitlementPlmnList(List<String> plmnList) throws Exception { SparseArray<List<String>> entitlementPlmnListPerCarrier = new SparseArray<>(); + if (!plmnList.isEmpty()) { + entitlementPlmnListPerCarrier.clear(); + entitlementPlmnListPerCarrier.put(SUB_ID, plmnList); + } replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier", mSatelliteControllerUT, entitlementPlmnListPerCarrier); + } - // If the carrier config and the entitlement plmn list are empty, verify whether an empty - // list is returned. - mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager - .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, - new PersistableBundle()); - for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair - : mCarrierConfigChangedListenerList) { - pair.first.execute(() -> pair.second.onCarrierConfigChanged( - /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0) - ); + private void setConfigDataPlmnList(List<String> plmnList) { + doReturn(plmnList).when(mMockConfig).getAllSatellitePlmnsForCarrier(anyInt()); + doReturn(mMockConfig).when(mMockConfigParser).getConfig(); + } + + private void setCarrierConfigDataPlmnList(List<String> plmnList) { + if (!plmnList.isEmpty()) { + mCarrierConfigBundle.putBoolean( + CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, + true); + PersistableBundle carrierSupportedSatelliteServicesPerProvider = + new PersistableBundle(); + List<String> carrierConfigPlmnList = plmnList; + carrierSupportedSatelliteServicesPerProvider.putIntArray( + carrierConfigPlmnList.get(0), new int[]{2}); + carrierSupportedSatelliteServicesPerProvider.putIntArray( + carrierConfigPlmnList.get(1), new int[]{1, 3}); + carrierSupportedSatelliteServicesPerProvider.putIntArray( + carrierConfigPlmnList.get(2), new int[]{2}); + mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager + .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, + carrierSupportedSatelliteServicesPerProvider); + } else { + mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager + .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, + new PersistableBundle()); } - processAllMessages(); - - List<String> plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier( - SUB_ID); - assertEquals(new ArrayList<>(), plmnListPerCarrier); + } - // If the carrier config list is empty and the entitlement plmn list is exists, verify - // whether the entitlement list is returned. - entitlementPlmnListPerCarrier.clear(); - List<String> entitlementPlmnList = Arrays.asList("00101", "00102", "00104"); - entitlementPlmnListPerCarrier.put(SUB_ID, entitlementPlmnList); - List<String> expectedPlmnListPerCarrier = entitlementPlmnList; + private void invokeCarrierConfigChanged() { for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair : mCarrierConfigChangedListenerList) { pair.first.execute(() -> pair.second.onCarrierConfigChanged( @@ -2803,58 +2889,61 @@ public class SatelliteControllerTest extends TelephonyTest { ); } processAllMessages(); + } + @Test + public void testUpdatePlmnListPerCarrier() throws Exception { + logd("testUpdatePlmnListPerCarrier"); + when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true); + replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier", + mSatelliteControllerUT, new SparseArray<>()); + List<String> plmnListPerCarrier; + + // verify whether an empty list is returned with conditions below + // the entitlement plmn list : empty + // the config data plmn list : empty + // the carrier config plmn list : empty + setEntitlementPlmnList(new ArrayList<>()); + setConfigDataPlmnList(new ArrayList<>()); + setCarrierConfigDataPlmnList(new ArrayList<>()); + invokeCarrierConfigChanged(); plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID); - assertEquals(expectedPlmnListPerCarrier, plmnListPerCarrier); - - // If the carrier config list is exists and the entitlement plmn list is empty, verify - // whether the carrier config list is returned. - entitlementPlmnListPerCarrier.clear(); - entitlementPlmnList = new ArrayList<>(); - entitlementPlmnListPerCarrier.put(SUB_ID, entitlementPlmnList); - mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, - true); - PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle(); - List<String> carrierConfigPlmnList = Arrays.asList("00102", "00103", "00105"); - carrierSupportedSatelliteServicesPerProvider.putIntArray( - carrierConfigPlmnList.get(0), new int[]{2}); - carrierSupportedSatelliteServicesPerProvider.putIntArray( - carrierConfigPlmnList.get(1), new int[]{1, 3}); - carrierSupportedSatelliteServicesPerProvider.putIntArray( - carrierConfigPlmnList.get(2), new int[]{2}); - mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager - .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, - carrierSupportedSatelliteServicesPerProvider); - for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair - : mCarrierConfigChangedListenerList) { - pair.first.execute(() -> pair.second.onCarrierConfigChanged( - /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0) - ); - } - processAllMessages(); - - expectedPlmnListPerCarrier = carrierConfigPlmnList; + assertEquals(new ArrayList<>(), plmnListPerCarrier.stream().sorted().toList()); + + // Verify whether the carrier config plmn list is returned with conditions below + // the entitlement plmn list : empty + // the config data plmn list : empty + // the carrier config plmn list : exist + setEntitlementPlmnList(new ArrayList<>()); + setConfigDataPlmnList(new ArrayList<>()); + setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104")); + invokeCarrierConfigChanged(); plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID); - assertEquals(expectedPlmnListPerCarrier.stream().sorted().toList(), + assertEquals(Arrays.asList("00101", "00102", "00104").stream().sorted().toList(), plmnListPerCarrier.stream().sorted().toList()); + // Verify whether config data plmn list is returned with conditions below + // the entitlement plmn list : empty + // the config data plmn list : exist + // the carrier config plmn list : exist + setEntitlementPlmnList(new ArrayList<>()); + setConfigDataPlmnList(Arrays.asList("11111", "22222", "33333")); + setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104")); + invokeCarrierConfigChanged(); + plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID); + assertEquals(Arrays.asList("11111", "22222", "33333").stream().sorted().toList(), + plmnListPerCarrier.stream().sorted().toList()); - // If the carrier config and the entitlement plmn list are exist, verify whether the - // entitlement list is returned. - entitlementPlmnList = Arrays.asList("00101", "00102", "00104"); - entitlementPlmnListPerCarrier.put(SUB_ID, entitlementPlmnList); - for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair - : mCarrierConfigChangedListenerList) { - pair.first.execute(() -> pair.second.onCarrierConfigChanged( - /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0) - ); - } - processAllMessages(); - - expectedPlmnListPerCarrier = entitlementPlmnList; - plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier( - SUB_ID); - assertEquals(expectedPlmnListPerCarrier.stream().sorted().toList(), + // Verify whether the entitlement plmn list is returned with conditions below + // the entitlement plmn list : exist + // the config data plmn list : exist + // the carrier config plmn list : exist + setEntitlementPlmnList(Arrays.asList("99090", "88080", "77070")); + setConfigDataPlmnList(Arrays.asList("11111", "22222", "33333")); + setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104")); + invokeCarrierConfigChanged(); + plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID); + assertEquals(Arrays.asList("99090", "88080", "77070").stream().sorted().toList(), plmnListPerCarrier.stream().sorted().toList()); } |