| /* |
| * Copyright 2018 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.data; |
| |
| import android.annotation.CallbackExecutor; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.StringDef; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.ServiceConnection; |
| import android.content.SharedPreferences; |
| import android.content.pm.PackageManager; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.PersistableBundle; |
| import android.os.Registrant; |
| import android.os.RegistrantList; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.os.UserHandle; |
| import android.preference.PreferenceManager; |
| import android.telephony.AccessNetworkConstants; |
| import android.telephony.AccessNetworkConstants.AccessNetworkType; |
| import android.telephony.AccessNetworkConstants.RadioAccessNetworkType; |
| import android.telephony.AccessNetworkConstants.TransportType; |
| import android.telephony.Annotation.ApnType; |
| import android.telephony.Annotation.NetCapability; |
| import android.telephony.AnomalyReporter; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.data.ApnSetting; |
| import android.telephony.data.IQualifiedNetworksService; |
| import android.telephony.data.IQualifiedNetworksServiceCallback; |
| import android.telephony.data.QualifiedNetworksService; |
| import android.telephony.data.ThrottleStatus; |
| import android.text.TextUtils; |
| import android.util.ArraySet; |
| import android.util.IndentingPrintWriter; |
| import android.util.LocalLog; |
| import android.util.SparseArray; |
| |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.RIL; |
| import com.android.internal.telephony.SlidingWindowEventCounter; |
| import com.android.internal.telephony.dataconnection.DataThrottler; |
| import com.android.telephony.Rlog; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.Executor; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Access network manager manages the qualified/available networks for mobile data connection. |
| * It binds to the vendor's qualified networks service and actively monitors the qualified |
| * networks changes. |
| */ |
| public class AccessNetworksManager extends Handler { |
| private static final boolean DBG = false; |
| private static final String APN_TRANSPORT = "apn_transport-%d-%d"; |
| public static final String SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE = |
| "ro.telephony.iwlan_operation_mode"; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @StringDef(prefix = {"IWLAN_OPERATION_MODE_"}, |
| value = { |
| IWLAN_OPERATION_MODE_DEFAULT, |
| IWLAN_OPERATION_MODE_LEGACY, |
| IWLAN_OPERATION_MODE_AP_ASSISTED}) |
| public @interface IwlanOperationMode {} |
| |
| /** |
| * IWLAN default mode. On device that has IRadio 1.4 or above, it means |
| * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.3 or below, it means |
| * {@link #IWLAN_OPERATION_MODE_LEGACY}. |
| */ |
| public static final String IWLAN_OPERATION_MODE_DEFAULT = "default"; |
| |
| /** |
| * IWLAN legacy mode. IWLAN is completely handled by the modem, and when the device is on |
| * IWLAN, modem reports IWLAN as a RAT. |
| */ |
| public static final String IWLAN_OPERATION_MODE_LEGACY = "legacy"; |
| |
| /** |
| * IWLAN application processor assisted mode. IWLAN is handled by the bound IWLAN data service |
| * and network service separately. |
| */ |
| public static final String IWLAN_OPERATION_MODE_AP_ASSISTED = "AP-assisted"; |
| |
| /** |
| * The counters to detect frequent QNS attempt to change preferred network transport by ApnType. |
| */ |
| private final @NonNull SparseArray<SlidingWindowEventCounter> mApnTypeToQnsChangeNetworkCounter; |
| |
| private final String mLogTag; |
| private final LocalLog mLocalLog = new LocalLog(64); |
| private final UUID mAnomalyUUID = UUID.fromString("c2d1a639-00e2-4561-9619-6acf37d90590"); |
| private String mLastBoundPackageName; |
| |
| public static final int[] SUPPORTED_APN_TYPES = { |
| ApnSetting.TYPE_DEFAULT, |
| ApnSetting.TYPE_MMS, |
| ApnSetting.TYPE_FOTA, |
| ApnSetting.TYPE_IMS, |
| ApnSetting.TYPE_CBS, |
| ApnSetting.TYPE_SUPL, |
| ApnSetting.TYPE_EMERGENCY, |
| ApnSetting.TYPE_XCAP, |
| ApnSetting.TYPE_DUN |
| }; |
| |
| private final Phone mPhone; |
| |
| private final CarrierConfigManager mCarrierConfigManager; |
| |
| private @Nullable DataConfigManager mDataConfigManager; |
| |
| private IQualifiedNetworksService mIQualifiedNetworksService; |
| |
| private AccessNetworksManagerDeathRecipient mDeathRecipient; |
| |
| private String mTargetBindingPackageName; |
| |
| private QualifiedNetworksServiceConnection mServiceConnection; |
| |
| // Available networks. Key is the APN type. |
| private final SparseArray<int[]> mAvailableNetworks = new SparseArray<>(); |
| |
| private final @TransportType int[] mAvailableTransports; |
| |
| private final RegistrantList mQualifiedNetworksChangedRegistrants = new RegistrantList(); |
| |
| private final Set<DataThrottler> mDataThrottlers = new HashSet<>(); |
| |
| private final BroadcastReceiver mConfigChangedReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| final String action = intent.getAction(); |
| if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action) |
| && mPhone.getPhoneId() == intent.getIntExtra( |
| CarrierConfigManager.EXTRA_SLOT_INDEX, 0)) { |
| // We should wait for carrier config changed event because the target binding |
| // package name can come from the carrier config. Note that we still get this event |
| // even when SIM is absent. |
| if (DBG) log("Carrier config changed. Try to bind qualified network service."); |
| bindQualifiedNetworksService(); |
| } |
| } |
| }; |
| |
| /** |
| * The current transport of the APN type. The key is the APN type, and the value is the |
| * transport. |
| */ |
| private final Map<Integer, Integer> mCurrentTransports = new ConcurrentHashMap<>(); |
| |
| /** |
| * The preferred transport of the APN type. The key is the APN type, and the value is the |
| * transport. The preferred transports are updated as soon as QNS changes the preference, while |
| * the current transports are updated after handover complete. |
| */ |
| // TODO: Deprecate mPreferredTransports. Should expose mAvailableNetworks to |
| // DataNetworkController after we support multi preferred access networks (i.e. |
| // DataNetworkController might select 2nd preferred access network in some scenarios.) |
| private final Map<Integer, Integer> mPreferredTransports = new ConcurrentHashMap<>(); |
| |
| /** |
| * Callbacks for passing information to interested clients. |
| */ |
| private final @NonNull Set<AccessNetworksManagerCallback> mAccessNetworksManagerCallbacks = |
| new ArraySet<>(); |
| |
| /** |
| * Registers the data throttler in order to receive APN status changes. |
| * |
| * @param dataThrottler the data throttler to register |
| */ |
| public void registerDataThrottler(DataThrottler dataThrottler) { |
| this.post(() -> { |
| QualifiedNetworksServiceConnection serviceConnection = mServiceConnection; |
| this.mDataThrottlers.add(dataThrottler); |
| if (serviceConnection != null) { |
| serviceConnection.registerDataThrottler(dataThrottler); |
| } |
| }); |
| } |
| |
| /** |
| * Represents qualified network types list on a specific APN type. |
| */ |
| public static class QualifiedNetworks { |
| public final @ApnType int apnType; |
| // The qualified networks in preferred order. Each network is a AccessNetworkType. |
| public final @NonNull @RadioAccessNetworkType int[] qualifiedNetworks; |
| public QualifiedNetworks(@ApnType int apnType, @NonNull int[] qualifiedNetworks) { |
| this.apnType = apnType; |
| this.qualifiedNetworks = Arrays.stream(qualifiedNetworks) |
| .boxed() |
| .filter(DataUtils::isValidAccessNetwork) |
| .mapToInt(Integer::intValue) |
| .toArray(); |
| } |
| |
| @Override |
| public String toString() { |
| return "[QualifiedNetworks: apnType=" |
| + ApnSetting.getApnTypeString(apnType) |
| + ", networks=" |
| + Arrays.stream(qualifiedNetworks) |
| .mapToObj(AccessNetworkType::toString) |
| .collect(Collectors.joining(",")) |
| + "]"; |
| } |
| } |
| |
| private class AccessNetworksManagerDeathRecipient implements IBinder.DeathRecipient { |
| @Override |
| public void binderDied() { |
| // TODO: try to rebind the service. |
| String message = "Qualified network service " + mLastBoundPackageName + " died."; |
| // clear the anomaly report counters when QNS crash |
| mApnTypeToQnsChangeNetworkCounter.clear(); |
| loge(message); |
| AnomalyReporter.reportAnomaly(mAnomalyUUID, message, mPhone.getCarrierId()); |
| } |
| } |
| |
| private final class QualifiedNetworksServiceConnection implements ServiceConnection { |
| |
| /** |
| * The APN throttle status callback is attached to the service connection so that they have |
| * the same life cycle. |
| */ |
| @NonNull |
| private final ThrottleStatusChangedCallback mThrottleStatusCallback; |
| |
| QualifiedNetworksServiceConnection() { |
| mThrottleStatusCallback = new ThrottleStatusChangedCallback(); |
| } |
| |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| if (DBG) log("onServiceConnected " + name); |
| mIQualifiedNetworksService = IQualifiedNetworksService.Stub.asInterface(service); |
| mDeathRecipient = new AccessNetworksManagerDeathRecipient(); |
| mLastBoundPackageName = getQualifiedNetworksServicePackageName(); |
| |
| try { |
| service.linkToDeath(mDeathRecipient, 0 /* flags */); |
| mIQualifiedNetworksService.createNetworkAvailabilityProvider(mPhone.getPhoneId(), |
| new QualifiedNetworksServiceCallback()); |
| |
| registerDataThrottlersFirstTime(); |
| |
| } catch (RemoteException e) { |
| loge("Remote exception. " + e); |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| if (DBG) log("onServiceDisconnected " + name); |
| unregisterForThrottleCallbacks(); |
| mTargetBindingPackageName = null; |
| } |
| |
| /** |
| * Runs on all of the data throttlers when the service is connected |
| */ |
| private void registerDataThrottlersFirstTime() { |
| post(() -> { |
| for (DataThrottler dataThrottler : mDataThrottlers) { |
| dataThrottler.registerForThrottleStatusChanges(mThrottleStatusCallback); |
| } |
| }); |
| } |
| |
| private void registerDataThrottler(DataThrottler dataThrottler) { |
| post(() -> { |
| dataThrottler.registerForThrottleStatusChanges(mThrottleStatusCallback); |
| }); |
| } |
| |
| private void unregisterForThrottleCallbacks() { |
| post(() -> { |
| for (DataThrottler dataThrottler : mDataThrottlers) { |
| dataThrottler.unregisterForThrottleStatusChanges(mThrottleStatusCallback); |
| } |
| }); |
| } |
| } |
| |
| private class ThrottleStatusChangedCallback implements DataThrottler.Callback { |
| @Override |
| public void onThrottleStatusChanged(List<ThrottleStatus> throttleStatuses) { |
| post(() -> { |
| try { |
| List<ThrottleStatus> throttleStatusesBySlot = |
| throttleStatuses |
| .stream() |
| .filter(x -> x.getSlotIndex() == mPhone.getPhoneId()) |
| .collect(Collectors.toList()); |
| if (mIQualifiedNetworksService != null) { |
| mIQualifiedNetworksService.reportThrottleStatusChanged(mPhone.getPhoneId(), |
| throttleStatusesBySlot); |
| } |
| } catch (Exception ex) { |
| loge("onThrottleStatusChanged", ex); |
| } |
| }); |
| } |
| } |
| |
| private final class QualifiedNetworksServiceCallback extends |
| IQualifiedNetworksServiceCallback.Stub { |
| @Override |
| public void onQualifiedNetworkTypesChanged(int apnTypes, |
| @NonNull int[] qualifiedNetworkTypes) { |
| if (qualifiedNetworkTypes == null) { |
| loge("onQualifiedNetworkTypesChanged: Ignored null input."); |
| return; |
| } |
| |
| log("onQualifiedNetworkTypesChanged: apnTypes = [" |
| + ApnSetting.getApnTypesStringFromBitmask(apnTypes) |
| + "], networks = [" + Arrays.stream(qualifiedNetworkTypes) |
| .mapToObj(AccessNetworkType::toString).collect(Collectors.joining(",")) |
| + "]"); |
| |
| if (Arrays.stream(qualifiedNetworkTypes).anyMatch(accessNetwork |
| -> !DataUtils.isValidAccessNetwork(accessNetwork))) { |
| loge("Invalid access networks " + Arrays.toString(qualifiedNetworkTypes)); |
| if (mDataConfigManager != null |
| && mDataConfigManager.isInvalidQnsParamAnomalyReportEnabled()) { |
| reportAnomaly("QNS requested invalid Network Type", |
| "3e89a3df-3524-45fa-b5f2-0fb0e4c77ec4"); |
| } |
| return; |
| } |
| |
| List<QualifiedNetworks> qualifiedNetworksList = new ArrayList<>(); |
| int satisfiedApnTypes = 0; |
| for (int apnType : SUPPORTED_APN_TYPES) { |
| if ((apnTypes & apnType) == apnType) { |
| // skip the APN anomaly detection if not using the T data stack |
| if (mDataConfigManager != null) { |
| satisfiedApnTypes |= apnType; |
| } |
| |
| if (mAvailableNetworks.get(apnType) != null) { |
| if (Arrays.equals(mAvailableNetworks.get(apnType), |
| qualifiedNetworkTypes)) { |
| log("Available networks for " |
| + ApnSetting.getApnTypesStringFromBitmask(apnType) |
| + " not changed."); |
| continue; |
| } |
| } |
| |
| // Empty array indicates QNS did not suggest any qualified networks. In this |
| // case all network requests will be routed to cellular. |
| if (qualifiedNetworkTypes.length == 0) { |
| mAvailableNetworks.remove(apnType); |
| if (getPreferredTransport(apnType) |
| == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { |
| mPreferredTransports.put(apnType, |
| AccessNetworkConstants.TRANSPORT_TYPE_WWAN); |
| mAccessNetworksManagerCallbacks.forEach(callback -> |
| callback.invokeFromExecutor(() -> |
| callback.onPreferredTransportChanged(DataUtils |
| .apnTypeToNetworkCapability(apnType)))); |
| } |
| } else { |
| mAvailableNetworks.put(apnType, qualifiedNetworkTypes); |
| qualifiedNetworksList.add(new QualifiedNetworks(apnType, |
| qualifiedNetworkTypes)); |
| |
| } |
| } |
| } |
| |
| // Report anomaly if any requested APN types are unsatisfied |
| if (satisfiedApnTypes != apnTypes |
| && mDataConfigManager != null |
| && mDataConfigManager.isInvalidQnsParamAnomalyReportEnabled()) { |
| int unsatisfied = satisfiedApnTypes ^ apnTypes; |
| reportAnomaly("QNS requested unsupported APN Types:" |
| + Integer.toBinaryString(unsatisfied), |
| "3e89a3df-3524-45fa-b5f2-0fb0e4c77ec5"); |
| } |
| |
| if (!qualifiedNetworksList.isEmpty()) { |
| setPreferredTransports(qualifiedNetworksList); |
| mQualifiedNetworksChangedRegistrants.notifyResult(qualifiedNetworksList); |
| } |
| } |
| } |
| |
| /** |
| * Access networks manager callback. This should be only used by {@link DataNetworkController}. |
| */ |
| public abstract static class AccessNetworksManagerCallback extends DataCallback { |
| /** |
| * Constructor |
| * |
| * @param executor The executor of the callback. |
| */ |
| public AccessNetworksManagerCallback(@NonNull @CallbackExecutor Executor executor) { |
| super(executor); |
| } |
| |
| /** |
| * Called when preferred transport changed. |
| * |
| * @param networkCapability The network capability. |
| */ |
| public abstract void onPreferredTransportChanged(@NetCapability int networkCapability); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param phone The phone object. |
| * @param looper Looper for the handler. |
| */ |
| public AccessNetworksManager(@NonNull Phone phone, @NonNull Looper looper) { |
| super(looper); |
| mPhone = phone; |
| mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService( |
| Context.CARRIER_CONFIG_SERVICE); |
| mLogTag = "ANM-" + mPhone.getPhoneId(); |
| mApnTypeToQnsChangeNetworkCounter = new SparseArray<>(); |
| |
| if (isInLegacyMode()) { |
| log("operates in legacy mode."); |
| // For legacy mode, WWAN is the only transport to handle all data connections, even |
| // the IWLAN ones. |
| mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN}; |
| } else { |
| log("operates in AP-assisted mode."); |
| mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN, |
| AccessNetworkConstants.TRANSPORT_TYPE_WLAN}; |
| IntentFilter intentFilter = new IntentFilter(); |
| intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); |
| try { |
| Context contextAsUser = phone.getContext().createPackageContextAsUser( |
| phone.getContext().getPackageName(), 0, UserHandle.ALL); |
| contextAsUser.registerReceiver(mConfigChangedReceiver, intentFilter, |
| null /* broadcastPermission */, null); |
| } catch (PackageManager.NameNotFoundException e) { |
| loge("Package name not found: ", e); |
| } |
| bindQualifiedNetworksService(); |
| } |
| |
| if (phone.isUsingNewDataStack()) { |
| // Using post to delay the registering because data retry manager and data config |
| // manager instances are created later than access networks manager. |
| post(() -> { |
| mPhone.getDataNetworkController().getDataRetryManager().registerCallback( |
| new DataRetryManager.DataRetryManagerCallback(this::post) { |
| @Override |
| public void onThrottleStatusChanged(List<ThrottleStatus> throttleStatuses) { |
| try { |
| logl("onThrottleStatusChanged: " + throttleStatuses); |
| if (mIQualifiedNetworksService != null) { |
| mIQualifiedNetworksService.reportThrottleStatusChanged( |
| mPhone.getPhoneId(), throttleStatuses); |
| } |
| } catch (Exception ex) { |
| loge("onThrottleStatusChanged: ", ex); |
| } |
| } |
| }); |
| mDataConfigManager = mPhone.getDataNetworkController().getDataConfigManager(); |
| mDataConfigManager.registerCallback( |
| new DataConfigManager.DataConfigManagerCallback(this::post) { |
| @Override |
| public void onDeviceConfigChanged() { |
| mApnTypeToQnsChangeNetworkCounter.clear(); |
| } |
| }); |
| }); |
| } |
| } |
| |
| /** |
| * Trigger the anomaly report with the specified UUID. |
| * |
| * @param anomalyMsg Description of the event |
| * @param uuid UUID associated with that event |
| */ |
| private void reportAnomaly(@NonNull String anomalyMsg, @NonNull String uuid) { |
| logl(anomalyMsg); |
| AnomalyReporter.reportAnomaly(UUID.fromString(uuid), anomalyMsg, mPhone.getCarrierId()); |
| } |
| |
| /** |
| * Find the qualified network service from configuration and binds to it. It reads the |
| * configuration from carrier config if it exists. If not, read it from resources. |
| */ |
| private void bindQualifiedNetworksService() { |
| post(() -> { |
| Intent intent = null; |
| String packageName = getQualifiedNetworksServicePackageName(); |
| String className = getQualifiedNetworksServiceClassName(); |
| |
| if (DBG) log("Qualified network service package = " + packageName); |
| if (TextUtils.isEmpty(packageName)) { |
| loge("Can't find the binding package"); |
| return; |
| } |
| |
| if (TextUtils.isEmpty(className)) { |
| intent = new Intent(QualifiedNetworksService.QUALIFIED_NETWORKS_SERVICE_INTERFACE); |
| intent.setPackage(packageName); |
| } else { |
| ComponentName cm = new ComponentName(packageName, className); |
| intent = new Intent(QualifiedNetworksService.QUALIFIED_NETWORKS_SERVICE_INTERFACE) |
| .setComponent(cm); |
| } |
| |
| if (TextUtils.equals(packageName, mTargetBindingPackageName)) { |
| if (DBG) log("Service " + packageName + " already bound or being bound."); |
| return; |
| } |
| |
| if (mIQualifiedNetworksService != null |
| && mIQualifiedNetworksService.asBinder().isBinderAlive()) { |
| // Remove the network availability updater and then unbind the service. |
| try { |
| mIQualifiedNetworksService.removeNetworkAvailabilityProvider( |
| mPhone.getPhoneId()); |
| } catch (RemoteException e) { |
| loge("Cannot remove network availability updater. " + e); |
| } |
| |
| mPhone.getContext().unbindService(mServiceConnection); |
| } |
| |
| try { |
| mServiceConnection = new QualifiedNetworksServiceConnection(); |
| log("bind to " + packageName); |
| if (!mPhone.getContext().bindService(intent, mServiceConnection, |
| Context.BIND_AUTO_CREATE)) { |
| loge("Cannot bind to the qualified networks service."); |
| return; |
| } |
| mTargetBindingPackageName = packageName; |
| } catch (Exception e) { |
| loge("Cannot bind to the qualified networks service. Exception: " + e); |
| } |
| }); |
| } |
| |
| /** |
| * Get the qualified network service package. |
| * |
| * @return package name of the qualified networks service package. Return empty string when in |
| * legacy mode (i.e. Dedicated IWLAN data/network service is not supported). |
| */ |
| private String getQualifiedNetworksServicePackageName() { |
| // Read package name from the resource |
| String packageName = mPhone.getContext().getResources().getString( |
| com.android.internal.R.string.config_qualified_networks_service_package); |
| |
| PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId()); |
| |
| if (b != null) { |
| // If carrier config overrides it, use the one from carrier config |
| String carrierConfigPackageName = b.getString(CarrierConfigManager |
| .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING); |
| if (!TextUtils.isEmpty(carrierConfigPackageName)) { |
| if (DBG) log("Found carrier config override " + carrierConfigPackageName); |
| packageName = carrierConfigPackageName; |
| } |
| } |
| |
| return packageName; |
| } |
| |
| /** |
| * Get the qualified network service class name. |
| * |
| * @return class name of the qualified networks service package. |
| */ |
| private String getQualifiedNetworksServiceClassName() { |
| // Read package name from the resource |
| String className = mPhone.getContext().getResources().getString( |
| com.android.internal.R.string.config_qualified_networks_service_class); |
| |
| PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId()); |
| |
| if (b != null) { |
| // If carrier config overrides it, use the one from carrier config |
| String carrierConfigClassName = b.getString(CarrierConfigManager |
| .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING); |
| if (!TextUtils.isEmpty(carrierConfigClassName)) { |
| if (DBG) log("Found carrier config override " + carrierConfigClassName); |
| className = carrierConfigClassName; |
| } |
| } |
| |
| return className; |
| } |
| |
| private @NonNull List<QualifiedNetworks> getQualifiedNetworksList() { |
| List<QualifiedNetworks> qualifiedNetworksList = new ArrayList<>(); |
| for (int i = 0; i < mAvailableNetworks.size(); i++) { |
| qualifiedNetworksList.add(new QualifiedNetworks(mAvailableNetworks.keyAt(i), |
| mAvailableNetworks.valueAt(i))); |
| } |
| |
| return qualifiedNetworksList; |
| } |
| |
| /** |
| * Register for qualified networks changed event. |
| * |
| * @param h The target to post the event message to. |
| * @param what The event. |
| */ |
| public void registerForQualifiedNetworksChanged(Handler h, int what) { |
| if (h != null) { |
| Registrant r = new Registrant(h, what, null); |
| mQualifiedNetworksChangedRegistrants.add(r); |
| |
| // Notify for the first time if there is already something in the available network |
| // list. |
| if (mAvailableNetworks.size() != 0) { |
| r.notifyResult(getQualifiedNetworksList()); |
| } |
| } |
| } |
| |
| /** |
| * @return {@code true} if the device operates in legacy mode, otherwise {@code false}. |
| */ |
| public boolean isInLegacyMode() { |
| // Get IWLAN operation mode from the system property. If the system property is configured |
| // to default or not configured, the mode is tied to IRadio version. For 1.4 or above, it's |
| // AP-assisted mode, for 1.3 or below, it's legacy mode. |
| String mode = SystemProperties.get(SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE); |
| |
| if (mode.equals(IWLAN_OPERATION_MODE_AP_ASSISTED)) { |
| return false; |
| } else if (mode.equals(IWLAN_OPERATION_MODE_LEGACY)) { |
| return true; |
| } |
| |
| return mPhone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4); |
| } |
| |
| /** |
| * @return The available transports. Note that on legacy devices, the only available transport |
| * would be WWAN only. If the device is configured as AP-assisted mode, the available transport |
| * will always be WWAN and WLAN (even if the device is not camped on IWLAN). |
| * See {@link #isInLegacyMode()} for mode details. |
| */ |
| public synchronized @NonNull int[] getAvailableTransports() { |
| return mAvailableTransports; |
| } |
| |
| /** |
| * Get the transport based on the network capability. |
| * |
| * @param netCap The network capability. |
| * @return The transport type. |
| */ |
| public @TransportType int getCurrentTransportByNetworkCapability(@NetCapability int netCap) { |
| return getCurrentTransport(DataUtils.networkCapabilityToApnType(netCap)); |
| } |
| |
| /** |
| * Get the transport based on the APN type. |
| * |
| * @param apnType APN type |
| * @return The transport type |
| */ |
| // TODO: Remove this after TransportManager is removed. |
| public @TransportType int getCurrentTransport(@ApnType int apnType) { |
| // In legacy mode, always route to cellular. |
| if (isInLegacyMode()) { |
| return AccessNetworkConstants.TRANSPORT_TYPE_WWAN; |
| } |
| |
| // If we can't find the corresponding transport, always route to cellular. |
| if (!mCurrentTransports.containsKey(apnType)) { |
| final SharedPreferences sp |
| = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext()); |
| final String key = String.format(APN_TRANSPORT, apnType, mPhone.getPhoneId()); |
| final int transport = sp.getInt(key, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); |
| mCurrentTransports.put(apnType, transport); |
| logl("getCurrentTransport: apnType=" + ApnSetting.getApnTypeString(apnType) |
| + ", transport=" + AccessNetworkConstants.transportTypeToString(transport)); |
| return transport; |
| } |
| return mCurrentTransports.get(apnType); |
| } |
| |
| /** |
| * Set the current transport of a network capability. |
| * |
| * @param netCap The network capability. |
| * @param transport The transport. |
| */ |
| public void setCurrentTransportByNetworkCapability(@NetCapability int netCap, |
| @TransportType int transport) { |
| setCurrentTransport(DataUtils.networkCapabilityToApnType(netCap), transport); |
| } |
| |
| /** |
| * Set the current transport of apn type. |
| * |
| * @param apnType The APN type |
| * @param transport The transport. |
| */ |
| // TODO: Remove this after TransportManager is removed. |
| public void setCurrentTransport(@ApnType int apnType, @TransportType int transport) { |
| Integer previousTransport = mCurrentTransports.put(apnType, transport); |
| if (previousTransport == null || previousTransport != transport) { |
| logl("setCurrentTransport: apnType=" + ApnSetting.getApnTypeString(apnType) |
| + ", transport=" + AccessNetworkConstants.transportTypeToString(transport)); |
| } |
| final SharedPreferences sp |
| = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext()); |
| final SharedPreferences.Editor editor = sp.edit(); |
| final String key = String.format(APN_TRANSPORT, apnType, mPhone.getPhoneId()); |
| editor.putInt(key, transport); |
| boolean result = editor.commit(); |
| logl("setCurrentTransport: key=" + key + ", result=" + result); |
| } |
| |
| private static @TransportType int getTransportFromAccessNetwork(int accessNetwork) { |
| return accessNetwork == AccessNetworkType.IWLAN |
| ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN |
| : AccessNetworkConstants.TRANSPORT_TYPE_WWAN; |
| } |
| |
| private void setPreferredTransports(@NonNull List<QualifiedNetworks> networksList) { |
| for (QualifiedNetworks networks : networksList) { |
| if (networks.qualifiedNetworks.length > 0) { |
| int transport = getTransportFromAccessNetwork(networks.qualifiedNetworks[0]); |
| if (getPreferredTransport(networks.apnType) != transport) { |
| mPreferredTransports.put(networks.apnType, transport); |
| mAccessNetworksManagerCallbacks.forEach(callback -> |
| callback.invokeFromExecutor(() -> |
| callback.onPreferredTransportChanged(DataUtils |
| .apnTypeToNetworkCapability(networks.apnType)))); |
| logl("setPreferredTransports: apnType=" |
| + ApnSetting.getApnTypeString(networks.apnType) + ", transport=" |
| + AccessNetworkConstants.transportTypeToString(transport)); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Get the preferred transport. |
| * |
| * @param apnType APN type |
| * @return The preferred transport. |
| */ |
| public @TransportType int getPreferredTransport(@ApnType int apnType) { |
| // In legacy mode, always preferred on cellular. |
| if (isInLegacyMode()) { |
| return AccessNetworkConstants.TRANSPORT_TYPE_WWAN; |
| } |
| |
| return mPreferredTransports.get(apnType) == null |
| ? AccessNetworkConstants.TRANSPORT_TYPE_WWAN : mPreferredTransports.get(apnType); |
| } |
| |
| /** |
| * Get the preferred transport by network capability. |
| * |
| * @param networkCapability The network capability. (Note that only APN-type capabilities are |
| * supported. |
| * @return The preferred transport. |
| */ |
| public @TransportType int getPreferredTransportByNetworkCapability( |
| @NetCapability int networkCapability) { |
| int apnType = DataUtils.networkCapabilityToApnType(networkCapability); |
| // For non-APN type capabilities, always route to WWAN. |
| if (apnType == ApnSetting.TYPE_NONE) { |
| return AccessNetworkConstants.TRANSPORT_TYPE_WWAN; |
| } |
| return getPreferredTransport(apnType); |
| } |
| |
| /** |
| * Check if there is any APN type's current transport is on IWLAN. |
| * |
| * @return {@code true} if there is any APN is on IWLAN, otherwise {@code false}. |
| */ |
| public boolean isAnyApnOnIwlan() { |
| for (int apnType : AccessNetworksManager.SUPPORTED_APN_TYPES) { |
| if (mPhone.isUsingNewDataStack()) { |
| if (getPreferredTransport(apnType) == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { |
| return true; |
| } |
| } else { |
| if (getCurrentTransport(apnType) == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Unregister for qualified networks changed event. |
| * |
| * @param h The handler |
| */ |
| public void unregisterForQualifiedNetworksChanged(Handler h) { |
| if (h != null) { |
| mQualifiedNetworksChangedRegistrants.remove(h); |
| } |
| } |
| |
| /** |
| * Register the callback for receiving information from {@link AccessNetworksManager}. |
| * |
| * @param callback The callback. |
| */ |
| public void registerCallback(@NonNull AccessNetworksManagerCallback callback) { |
| mAccessNetworksManagerCallbacks.add(callback); |
| } |
| |
| /** |
| * Unregister the callback which was previously registered through |
| * {@link #registerCallback(AccessNetworksManagerCallback)}. |
| * |
| * @param callback The callback to unregister. |
| */ |
| public void unregisterCallback(@NonNull AccessNetworksManagerCallback callback) { |
| mAccessNetworksManagerCallbacks.remove(callback); |
| } |
| |
| private void log(String s) { |
| Rlog.d(mLogTag, s); |
| } |
| |
| private void loge(String s) { |
| Rlog.e(mLogTag, s); |
| } |
| |
| private void loge(String s, Exception ex) { |
| Rlog.e(mLogTag, s, ex); |
| } |
| |
| private void logl(String s) { |
| log(s); |
| mLocalLog.log(s); |
| } |
| |
| /** |
| * Dump the state of access networks manager |
| * |
| * @param fd File descriptor |
| * @param printWriter Print writer |
| * @param args Arguments |
| */ |
| public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { |
| IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); |
| pw.println(AccessNetworksManager.class.getSimpleName() + "-" + mPhone.getPhoneId() + ":"); |
| pw.increaseIndent(); |
| pw.println("current transports="); |
| pw.increaseIndent(); |
| for (int apnType : AccessNetworksManager.SUPPORTED_APN_TYPES) { |
| pw.println(ApnSetting.getApnTypeString(apnType) |
| + ": " + AccessNetworkConstants.transportTypeToString( |
| getCurrentTransport(apnType))); |
| } |
| pw.decreaseIndent(); |
| pw.println("preferred transports="); |
| pw.increaseIndent(); |
| for (int apnType : AccessNetworksManager.SUPPORTED_APN_TYPES) { |
| pw.println(ApnSetting.getApnTypeString(apnType) |
| + ": " + AccessNetworkConstants.transportTypeToString( |
| getPreferredTransport(apnType))); |
| } |
| |
| pw.decreaseIndent(); |
| pw.println("isInLegacy=" + isInLegacyMode()); |
| pw.println("IWLAN operation mode=" |
| + SystemProperties.get(SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE)); |
| pw.println("Local logs="); |
| pw.increaseIndent(); |
| mLocalLog.dump(fd, pw, args); |
| pw.decreaseIndent(); |
| pw.decreaseIndent(); |
| pw.flush(); |
| } |
| } |