| /* |
| * Copyright 2020 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.google.android.iwlan; |
| |
| import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; |
| import static android.net.NetworkCapabilities.TRANSPORT_WIFI; |
| import static android.net.ipsec.ike.ike3gpp.Ike3gppParams.PDU_SESSION_ID_UNSET; |
| |
| import static com.google.android.iwlan.epdg.EpdgTunnelManager.BRINGDOWN_REASON_DEACTIVATE_DATA_CALL; |
| import static com.google.android.iwlan.epdg.EpdgTunnelManager.BRINGDOWN_REASON_IN_DEACTIVATING_STATE; |
| import static com.google.android.iwlan.epdg.EpdgTunnelManager.BRINGDOWN_REASON_NETWORK_UPDATE_WHEN_TUNNEL_IN_BRINGUP; |
| import static com.google.android.iwlan.epdg.EpdgTunnelManager.BRINGDOWN_REASON_SERVICE_OUT_OF_SYNC; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.net.ConnectivityManager; |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.Network; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkSpecifier; |
| import android.net.TelephonyNetworkSpecifier; |
| import android.net.TransportInfo; |
| import android.net.vcn.VcnTransportInfo; |
| import android.net.wifi.WifiInfo; |
| import android.net.wifi.WifiManager; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.support.annotation.IntRange; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.telephony.AccessNetworkConstants.AccessNetworkType; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.CellInfo; |
| import android.telephony.CellInfoGsm; |
| import android.telephony.CellInfoLte; |
| import android.telephony.CellInfoNr; |
| import android.telephony.CellInfoWcdma; |
| import android.telephony.DataFailCause; |
| import android.telephony.TelephonyManager; |
| import android.telephony.data.ApnSetting; |
| import android.telephony.data.DataCallResponse; |
| import android.telephony.data.DataProfile; |
| import android.telephony.data.DataService; |
| import android.telephony.data.DataServiceCallback; |
| import android.telephony.data.NetworkSliceInfo; |
| import android.telephony.data.TrafficDescriptor; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import com.google.android.iwlan.TunnelMetricsInterface.OnClosedMetrics; |
| import com.google.android.iwlan.TunnelMetricsInterface.OnOpenedMetrics; |
| import com.google.android.iwlan.epdg.EpdgSelector; |
| import com.google.android.iwlan.epdg.EpdgTunnelManager; |
| import com.google.android.iwlan.epdg.TunnelLinkProperties; |
| import com.google.android.iwlan.epdg.TunnelSetupRequest; |
| import com.google.android.iwlan.flags.FeatureFlags; |
| import com.google.android.iwlan.flags.FeatureFlagsImpl; |
| import com.google.android.iwlan.proto.MetricsAtom; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.UnknownHostException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.LongSummaryStatistics; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| public class IwlanDataService extends DataService { |
| |
| private final FeatureFlags mFeatureFlags; |
| private static final String TAG = IwlanDataService.class.getSimpleName(); |
| |
| private static final String CONTEXT_ATTRIBUTION_TAG = "IWLAN"; |
| private static Context mContext; |
| private IwlanNetworkMonitorCallback mNetworkMonitorCallback; |
| private static boolean sNetworkConnected = false; |
| private static Network sNetwork = null; |
| private static LinkProperties sLinkProperties = null; |
| @VisibleForTesting Handler mIwlanDataServiceHandler; |
| private HandlerThread mIwlanDataServiceHandlerThread; |
| private static final Map<Integer, IwlanDataServiceProvider> sIwlanDataServiceProviders = |
| new ConcurrentHashMap<>(); |
| private static final int INVALID_SUB_ID = -1; |
| |
| // The current subscription with the active internet PDN. Need not be the default data sub. |
| // If internet is over WiFi, this value will be INVALID_SUB_ID. |
| private static int mConnectedDataSub = INVALID_SUB_ID; |
| |
| private static final int EVENT_BASE = IwlanEventListener.DATA_SERVICE_INTERNAL_EVENT_BASE; |
| private static final int EVENT_TUNNEL_OPENED = EVENT_BASE; |
| private static final int EVENT_TUNNEL_CLOSED = EVENT_BASE + 1; |
| private static final int EVENT_SETUP_DATA_CALL = EVENT_BASE + 2; |
| private static final int EVENT_DEACTIVATE_DATA_CALL = EVENT_BASE + 3; |
| private static final int EVENT_DATA_CALL_LIST_REQUEST = EVENT_BASE + 4; |
| private static final int EVENT_FORCE_CLOSE_TUNNEL = EVENT_BASE + 5; |
| private static final int EVENT_ADD_DATA_SERVICE_PROVIDER = EVENT_BASE + 6; |
| private static final int EVENT_REMOVE_DATA_SERVICE_PROVIDER = EVENT_BASE + 7; |
| private static final int EVENT_TUNNEL_OPENED_METRICS = EVENT_BASE + 8; |
| private static final int EVENT_TUNNEL_CLOSED_METRICS = EVENT_BASE + 9; |
| private static final int EVENT_DEACTIVATE_DATA_CALL_WITH_DELAY = EVENT_BASE + 10; |
| |
| @VisibleForTesting |
| enum Transport { |
| UNSPECIFIED_NETWORK, |
| MOBILE, |
| WIFI |
| } |
| |
| private static Transport sDefaultDataTransport = Transport.UNSPECIFIED_NETWORK; |
| |
| private boolean mIs5GEnabledOnUi; |
| |
| public IwlanDataService() { |
| mFeatureFlags = new FeatureFlagsImpl(); |
| } |
| |
| @VisibleForTesting |
| IwlanDataService(FeatureFlags featureFlags) { |
| mFeatureFlags = featureFlags; |
| } |
| |
| // TODO: see if network monitor callback impl can be shared between dataservice and |
| // networkservice |
| // This callback runs in the same thread as IwlanDataServiceHandler |
| static class IwlanNetworkMonitorCallback extends ConnectivityManager.NetworkCallback { |
| |
| /** Called when the framework connects and has declared a new network ready for use. */ |
| @Override |
| public void onAvailable(@NonNull Network network) { |
| Log.d(TAG, "onAvailable: " + network); |
| } |
| |
| /** |
| * Called when the network is about to be lost, typically because there are no outstanding |
| * requests left for it. This may be paired with a {@link NetworkCallback#onAvailable} call |
| * with the new replacement network for graceful handover. This method is not guaranteed to |
| * be called before {@link NetworkCallback#onLost} is called, for example in case a network |
| * is suddenly disconnected. |
| */ |
| @Override |
| public void onLosing(@NonNull Network network, int maxMsToLive) { |
| Log.d(TAG, "onLosing: maxMsToLive: " + maxMsToLive + " network: " + network); |
| } |
| |
| /** |
| * Called when a network disconnects or otherwise no longer satisfies this request or |
| * callback. |
| */ |
| @Override |
| public void onLost(@NonNull Network network) { |
| Log.d(TAG, "onLost: " + network); |
| IwlanDataService.setConnectedDataSub(INVALID_SUB_ID); |
| IwlanDataService.setNetworkConnected(false, network, Transport.UNSPECIFIED_NETWORK); |
| } |
| |
| /** Called when the network corresponding to this request changes {@link LinkProperties}. */ |
| @Override |
| public void onLinkPropertiesChanged( |
| @NonNull Network network, @NonNull LinkProperties linkProperties) { |
| Log.d(TAG, "onLinkPropertiesChanged: " + linkProperties); |
| |
| if (!network.equals(sNetwork)) { |
| Log.d(TAG, "Ignore LinkProperties changes for unused Network."); |
| return; |
| } |
| |
| if (!linkProperties.equals(sLinkProperties)) { |
| for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { |
| dp.dnsPrefetchCheck(); |
| sLinkProperties = linkProperties; |
| dp.updateNetwork(network, linkProperties); |
| } |
| } |
| } |
| |
| /** Called when access to the specified network is blocked or unblocked. */ |
| @Override |
| public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) { |
| // TODO: check if we need to handle this |
| Log.d(TAG, "onBlockedStatusChanged: " + network + " BLOCKED:" + blocked); |
| } |
| |
| @Override |
| public void onCapabilitiesChanged( |
| @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { |
| // onCapabilitiesChanged is guaranteed to be called immediately after onAvailable per |
| // API |
| Log.d(TAG, "onCapabilitiesChanged: " + network + " " + networkCapabilities); |
| if (networkCapabilities != null) { |
| if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { |
| Log.d(TAG, "Network " + network + " connected using transport MOBILE"); |
| IwlanDataService.setConnectedDataSub(getConnectedDataSub(networkCapabilities)); |
| IwlanDataService.setNetworkConnected(true, network, Transport.MOBILE); |
| } else if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) { |
| Log.d(TAG, "Network " + network + " connected using transport WIFI"); |
| IwlanDataService.setConnectedDataSub(INVALID_SUB_ID); |
| IwlanDataService.setNetworkConnected(true, network, Transport.WIFI); |
| } else { |
| Log.w(TAG, "Network does not have cellular or wifi capability"); |
| } |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| class IwlanDataServiceProvider extends DataService.DataServiceProvider { |
| private static final int CALLBACK_TYPE_SETUP_DATACALL_COMPLETE = 1; |
| private static final int CALLBACK_TYPE_DEACTIVATE_DATACALL_COMPLETE = 2; |
| private static final int CALLBACK_TYPE_GET_DATACALL_LIST_COMPLETE = 3; |
| |
| private final String SUB_TAG; |
| private final IwlanDataService mIwlanDataService; |
| private final IwlanTunnelCallback mIwlanTunnelCallback; |
| private final IwlanTunnelMetricsImpl mIwlanTunnelMetrics; |
| private boolean mWfcEnabled = false; |
| private boolean mCarrierConfigReady = false; |
| private final EpdgSelector mEpdgSelector; |
| private final IwlanDataTunnelStats mTunnelStats; |
| private CellInfo mCellInfo = null; |
| private int mCallState = TelephonyManager.CALL_STATE_IDLE; |
| private long mProcessingStartTime = 0; |
| |
| // apn to TunnelState |
| // Access should be serialized inside IwlanDataServiceHandler |
| private final Map<String, TunnelState> mTunnelStateForApn = new ConcurrentHashMap<>(); |
| private final Map<String, MetricsAtom> mMetricsAtomForApn = new ConcurrentHashMap<>(); |
| private Calendar mCalendar; |
| |
| // Holds the state of a tunnel (for an APN) |
| @VisibleForTesting |
| class TunnelState { |
| |
| // this should be ideally be based on path MTU discovery. 1280 is the minimum packet |
| // size ipv6 routers have to handle so setting it to 1280 is the safest approach. |
| // ideally it should be 1280 - tunnelling overhead ? |
| private static final int LINK_MTU = 1280; // TODO: need to subtract tunnelling overhead? |
| private static final int LINK_MTU_CST = 1200; // Reserve 80 bytes for VCN. |
| static final int TUNNEL_DOWN = 1; |
| static final int TUNNEL_IN_BRINGUP = 2; |
| static final int TUNNEL_UP = 3; |
| static final int TUNNEL_IN_BRINGDOWN = 4; |
| static final int TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP = 5; |
| private DataServiceCallback dataServiceCallback; |
| private int mState; |
| private int mPduSessionId; |
| private TunnelLinkProperties mTunnelLinkProperties; |
| private boolean mIsHandover; |
| private Date mBringUpStateTime = null; |
| private Date mUpStateTime = null; |
| private boolean mIsImsOrEmergency; |
| private DeactivateDataCallData mPendingDeactivateDataCallData; |
| private boolean mIsDataCallWithN1; |
| |
| public boolean getIsDataCallWithN1() { |
| return mIsDataCallWithN1; |
| } |
| |
| public void setIsDataCallWithN1(boolean mIsDataCallWithN1) { |
| this.mIsDataCallWithN1 = mIsDataCallWithN1; |
| } |
| |
| public int getPduSessionId() { |
| return mPduSessionId; |
| } |
| |
| public void setPduSessionId(int mPduSessionId) { |
| this.mPduSessionId = mPduSessionId; |
| } |
| |
| public int getLinkMtu() { |
| if ((sDefaultDataTransport == Transport.MOBILE) && sNetworkConnected) { |
| return LINK_MTU_CST; |
| } else { |
| return LINK_MTU; // TODO: need to subtract tunnelling overhead |
| } |
| } |
| |
| public @ApnSetting.ProtocolType int getRequestedProtocolType() { |
| return mProtocolType; |
| } |
| |
| public void setProtocolType(int protocolType) { |
| mProtocolType = protocolType; |
| } |
| |
| private int mProtocolType; // from DataProfile |
| |
| public TunnelLinkProperties getTunnelLinkProperties() { |
| return mTunnelLinkProperties; |
| } |
| |
| public void setTunnelLinkProperties(TunnelLinkProperties tunnelLinkProperties) { |
| mTunnelLinkProperties = tunnelLinkProperties; |
| } |
| |
| public DataServiceCallback getDataServiceCallback() { |
| return dataServiceCallback; |
| } |
| |
| public void setDataServiceCallback(DataServiceCallback dataServiceCallback) { |
| this.dataServiceCallback = dataServiceCallback; |
| } |
| |
| public TunnelState(DataServiceCallback callback) { |
| dataServiceCallback = callback; |
| mState = TUNNEL_DOWN; |
| } |
| |
| public int getState() { |
| return mState; |
| } |
| |
| public DeactivateDataCallData getPendingDeactivateDataCallData() { |
| return mPendingDeactivateDataCallData; |
| } |
| |
| public boolean hasPendingDeactivateDataCallData() { |
| return mPendingDeactivateDataCallData != null; |
| } |
| |
| /** |
| * @param state (TunnelState.TUNNEL_DOWN|TUNNEL_UP|TUNNEL_DOWN) |
| */ |
| public void setState(int state) { |
| mState = state; |
| if (mState == TunnelState.TUNNEL_IN_BRINGUP) { |
| mBringUpStateTime = mCalendar.getTime(); |
| } |
| if (mState == TunnelState.TUNNEL_UP) { |
| mUpStateTime = mCalendar.getTime(); |
| } |
| } |
| |
| public void setIsHandover(boolean isHandover) { |
| mIsHandover = isHandover; |
| } |
| |
| public boolean getIsHandover() { |
| return mIsHandover; |
| } |
| |
| public Date getBringUpStateTime() { |
| return mBringUpStateTime; |
| } |
| |
| public Date getUpStateTime() { |
| return mUpStateTime; |
| } |
| |
| public Date getCurrentTime() { |
| return mCalendar.getTime(); |
| } |
| |
| public boolean getIsImsOrEmergency() { |
| return mIsImsOrEmergency; |
| } |
| |
| public void setIsImsOrEmergency(boolean isImsOrEmergency) { |
| mIsImsOrEmergency = isImsOrEmergency; |
| } |
| |
| public void setPendingDeactivateDataCallData( |
| DeactivateDataCallData deactivateDataCallData) { |
| mPendingDeactivateDataCallData = deactivateDataCallData; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| String tunnelState = "UNKNOWN"; |
| switch (mState) { |
| case TUNNEL_DOWN: |
| tunnelState = "DOWN"; |
| break; |
| case TUNNEL_IN_BRINGUP: |
| tunnelState = "IN BRINGUP"; |
| break; |
| case TUNNEL_UP: |
| tunnelState = "UP"; |
| break; |
| case TUNNEL_IN_BRINGDOWN: |
| tunnelState = "IN BRINGDOWN"; |
| break; |
| case TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP: |
| tunnelState = "IN FORCE CLEAN WAS IN BRINGUP"; |
| break; |
| } |
| sb.append("\tCurrent State of this tunnel: ") |
| .append(mState) |
| .append(" ") |
| .append(tunnelState); |
| sb.append("\n\tTunnel state is in Handover: ").append(mIsHandover); |
| if (mBringUpStateTime != null) { |
| sb.append("\n\tTunnel bring up initiated at: ").append(mBringUpStateTime); |
| } else { |
| sb.append("\n\tPotential leak. Null mBringUpStateTime"); |
| } |
| if (mUpStateTime != null) { |
| sb.append("\n\tTunnel is up at: ").append(mUpStateTime); |
| } |
| if (mUpStateTime != null && mBringUpStateTime != null) { |
| long tunnelUpTime = mUpStateTime.getTime() - mBringUpStateTime.getTime(); |
| sb.append("\n\tTime taken for the tunnel to come up in ms: ") |
| .append(tunnelUpTime); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| @VisibleForTesting |
| class IwlanTunnelCallback implements EpdgTunnelManager.TunnelCallback { |
| |
| IwlanDataServiceProvider mIwlanDataServiceProvider; |
| |
| public IwlanTunnelCallback(IwlanDataServiceProvider dsp) { |
| mIwlanDataServiceProvider = dsp; |
| } |
| |
| // TODO: full implementation |
| |
| public void onOpened(String apnName, TunnelLinkProperties linkProperties) { |
| Log.d( |
| SUB_TAG, |
| "Tunnel opened! APN: " + apnName + ", linkProperties: " + linkProperties); |
| getIwlanDataServiceHandler() |
| .sendMessage( |
| getIwlanDataServiceHandler() |
| .obtainMessage( |
| EVENT_TUNNEL_OPENED, |
| new TunnelOpenedData( |
| apnName, |
| linkProperties, |
| mIwlanDataServiceProvider))); |
| } |
| |
| public void onClosed(String apnName, IwlanError error) { |
| Log.d(SUB_TAG, "Tunnel closed! APN: " + apnName + ", Error: " + error); |
| // this is called, when a tunnel that is up, is closed. |
| // the expectation is error==NO_ERROR for user initiated/normal close. |
| getIwlanDataServiceHandler() |
| .sendMessage( |
| getIwlanDataServiceHandler() |
| .obtainMessage( |
| EVENT_TUNNEL_CLOSED, |
| new TunnelClosedData( |
| apnName, |
| error, |
| mIwlanDataServiceProvider))); |
| } |
| } |
| |
| /** Holds all tunnel related time and count statistics for this IwlanDataServiceProvider */ |
| @VisibleForTesting |
| class IwlanDataTunnelStats { |
| |
| // represents the start time from when the following events are recorded |
| private Date mStartTime; |
| |
| // Stats for TunnelSetup Success time (BRING_UP -> UP state) |
| @VisibleForTesting |
| Map<String, LongSummaryStatistics> mTunnelSetupSuccessStats = |
| new HashMap<String, LongSummaryStatistics>(); |
| // Count for Tunnel Setup failures onClosed when in BRING_UP |
| @VisibleForTesting |
| Map<String, Long> mTunnelSetupFailureCounts = new HashMap<String, Long>(); |
| |
| // Count for unsol tunnel down onClosed when in UP without deactivate |
| @VisibleForTesting |
| Map<String, Long> mUnsolTunnelDownCounts = new HashMap<String, Long>(); |
| |
| // Stats for how long the tunnel is in up state onClosed when in UP |
| @VisibleForTesting |
| Map<String, LongSummaryStatistics> mTunnelUpStats = |
| new HashMap<String, LongSummaryStatistics>(); |
| |
| private long statCount; |
| private final long COUNT_MAX = 1000; |
| |
| public IwlanDataTunnelStats() { |
| mStartTime = mCalendar.getTime(); |
| statCount = 0L; |
| } |
| |
| public void reportTunnelSetupSuccess(String apn, TunnelState tunnelState) { |
| if (statCount > COUNT_MAX || maxApnReached()) { |
| reset(); |
| } |
| statCount++; |
| |
| Date bringUpTime = tunnelState.getBringUpStateTime(); |
| Date upTime = tunnelState.getUpStateTime(); |
| |
| if (bringUpTime != null && upTime != null) { |
| long tunnelUpTime = upTime.getTime() - bringUpTime.getTime(); |
| if (!mTunnelSetupSuccessStats.containsKey(apn)) { |
| mTunnelSetupSuccessStats.put(apn, new LongSummaryStatistics()); |
| } |
| LongSummaryStatistics stats = mTunnelSetupSuccessStats.get(apn); |
| stats.accept(tunnelUpTime); |
| mTunnelSetupSuccessStats.put(apn, stats); |
| } |
| } |
| |
| public void reportTunnelDown(String apn, TunnelState tunnelState) { |
| if (statCount > COUNT_MAX || maxApnReached()) { |
| reset(); |
| } |
| statCount++; |
| |
| // Setup fail |
| if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGUP) { |
| if (!mTunnelSetupFailureCounts.containsKey(apn)) { |
| mTunnelSetupFailureCounts.put(apn, 0L); |
| } |
| long count = mTunnelSetupFailureCounts.get(apn); |
| mTunnelSetupFailureCounts.put(apn, ++count); |
| return; |
| } |
| |
| // Unsolicited tunnel down as tunnel has to be in BRINGDOWN if |
| // there is a deactivateDataCall() associated with this. |
| if (tunnelState.getState() == TunnelState.TUNNEL_UP) { |
| if (!mUnsolTunnelDownCounts.containsKey(apn)) { |
| mUnsolTunnelDownCounts.put(apn, 0L); |
| } |
| long count = mUnsolTunnelDownCounts.get(apn); |
| mUnsolTunnelDownCounts.put(apn, ++count); |
| } |
| Date currentTime = tunnelState.getCurrentTime(); |
| Date upTime = tunnelState.getUpStateTime(); |
| if (upTime != null) { |
| if (!mTunnelUpStats.containsKey(apn)) { |
| mTunnelUpStats.put(apn, new LongSummaryStatistics()); |
| } |
| LongSummaryStatistics stats = mTunnelUpStats.get(apn); |
| stats.accept(currentTime.getTime() - upTime.getTime()); |
| mTunnelUpStats.put(apn, stats); |
| } |
| } |
| |
| boolean maxApnReached() { |
| int APN_COUNT_MAX = 10; |
| return mTunnelSetupSuccessStats.size() >= APN_COUNT_MAX |
| || mTunnelSetupFailureCounts.size() >= APN_COUNT_MAX |
| || mUnsolTunnelDownCounts.size() >= APN_COUNT_MAX |
| || mTunnelUpStats.size() >= APN_COUNT_MAX; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("IwlanDataTunnelStats:"); |
| sb.append("\n\tmStartTime: ").append(mStartTime); |
| sb.append("\n\ttunnelSetupSuccessStats:"); |
| for (Map.Entry<String, LongSummaryStatistics> entry : |
| mTunnelSetupSuccessStats.entrySet()) { |
| sb.append("\n\t Apn: ").append(entry.getKey()); |
| sb.append("\n\t ").append(entry.getValue()); |
| } |
| sb.append("\n\ttunnelUpStats:"); |
| for (Map.Entry<String, LongSummaryStatistics> entry : mTunnelUpStats.entrySet()) { |
| sb.append("\n\t Apn: ").append(entry.getKey()); |
| sb.append("\n\t ").append(entry.getValue()); |
| } |
| |
| sb.append("\n\ttunnelSetupFailureCounts: "); |
| for (Map.Entry<String, Long> entry : mTunnelSetupFailureCounts.entrySet()) { |
| sb.append("\n\t Apn: ").append(entry.getKey()); |
| sb.append("\n\t counts: ").append(entry.getValue()); |
| } |
| sb.append("\n\tunsolTunnelDownCounts: "); |
| for (Map.Entry<String, Long> entry : mTunnelSetupFailureCounts.entrySet()) { |
| sb.append("\n\t Apn: ").append(entry.getKey()); |
| sb.append("\n\t counts: ").append(entry.getValue()); |
| } |
| sb.append("\n\tendTime: ").append(mCalendar.getTime()); |
| return sb.toString(); |
| } |
| |
| private void reset() { |
| mStartTime = mCalendar.getTime(); |
| mTunnelSetupSuccessStats = new HashMap<String, LongSummaryStatistics>(); |
| mTunnelUpStats = new HashMap<String, LongSummaryStatistics>(); |
| mTunnelSetupFailureCounts = new HashMap<String, Long>(); |
| mUnsolTunnelDownCounts = new HashMap<String, Long>(); |
| statCount = 0L; |
| } |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param slotIndex SIM slot index the data service provider associated with. |
| */ |
| public IwlanDataServiceProvider(int slotIndex, IwlanDataService iwlanDataService) { |
| super(slotIndex); |
| SUB_TAG = TAG + "[" + slotIndex + "]"; |
| |
| // TODO: |
| // get reference carrier config for this sub |
| // get reference to resolver |
| mIwlanDataService = iwlanDataService; |
| mIwlanTunnelCallback = new IwlanTunnelCallback(this); |
| mIwlanTunnelMetrics = new IwlanTunnelMetricsImpl(this, getIwlanDataServiceHandler()); |
| mEpdgSelector = EpdgSelector.getSelectorInstance(mContext, slotIndex); |
| mCalendar = Calendar.getInstance(); |
| mTunnelStats = new IwlanDataTunnelStats(); |
| |
| // Register IwlanEventListener |
| List<Integer> events = new ArrayList<Integer>(); |
| events.add(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT); |
| events.add(IwlanEventListener.CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT); |
| events.add(IwlanEventListener.WIFI_CALLING_ENABLE_EVENT); |
| events.add(IwlanEventListener.WIFI_CALLING_DISABLE_EVENT); |
| events.add(IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT); |
| events.add(IwlanEventListener.CELLINFO_CHANGED_EVENT); |
| events.add(IwlanEventListener.CALL_STATE_CHANGED_EVENT); |
| events.add(IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT); |
| IwlanEventListener.getInstance(mContext, slotIndex) |
| .addEventListener(events, getIwlanDataServiceHandler()); |
| } |
| |
| private EpdgTunnelManager getTunnelManager() { |
| return EpdgTunnelManager.getInstance(mContext, getSlotIndex()); |
| } |
| |
| // creates a DataCallResponse for an apn irrespective of state |
| private DataCallResponse apnTunnelStateToDataCallResponse(String apn) { |
| TunnelState tunnelState = mTunnelStateForApn.get(apn); |
| if (tunnelState == null) { |
| return null; |
| } |
| |
| DataCallResponse.Builder responseBuilder = new DataCallResponse.Builder(); |
| int state = tunnelState.getState(); |
| TunnelLinkProperties tunnelLinkProperties = tunnelState.getTunnelLinkProperties(); |
| if (tunnelLinkProperties == null) { |
| Log.d(TAG, "PDN with empty linkProperties. TunnelState : " + state); |
| return responseBuilder.build(); |
| } |
| responseBuilder |
| .setId(apn.hashCode()) |
| .setProtocolType(tunnelLinkProperties.getProtocolType()) |
| .setCause(DataFailCause.NONE) |
| .setLinkStatus( |
| state == TunnelState.TUNNEL_UP |
| ? DataCallResponse.LINK_STATUS_ACTIVE |
| : DataCallResponse.LINK_STATUS_INACTIVE); |
| |
| // fill wildcard address for gatewayList (used by DataConnection to add routes) |
| List<InetAddress> gatewayList = new ArrayList<>(); |
| List<LinkAddress> linkAddrList = tunnelLinkProperties.internalAddresses(); |
| if (linkAddrList.stream().anyMatch(LinkAddress::isIpv4)) { |
| try { |
| gatewayList.add(Inet4Address.getByName("0.0.0.0")); |
| } catch (UnknownHostException e) { |
| // should never happen for static string 0.0.0.0 |
| } |
| } |
| if (linkAddrList.stream().anyMatch(LinkAddress::isIpv6)) { |
| try { |
| gatewayList.add(Inet6Address.getByName("::")); |
| } catch (UnknownHostException e) { |
| // should never happen for static string :: |
| } |
| } |
| |
| if (tunnelLinkProperties.sliceInfo().isPresent()) { |
| responseBuilder.setSliceInfo(tunnelLinkProperties.sliceInfo().get()); |
| } |
| |
| return responseBuilder |
| .setAddresses(linkAddrList) |
| .setDnsAddresses(tunnelLinkProperties.dnsAddresses()) |
| .setPcscfAddresses(tunnelLinkProperties.pcscfAddresses()) |
| .setInterfaceName(tunnelLinkProperties.ifaceName()) |
| .setGatewayAddresses(gatewayList) |
| .setMtu(tunnelState.getLinkMtu()) |
| .setMtuV4(tunnelState.getLinkMtu()) |
| .setMtuV6(tunnelState.getLinkMtu()) |
| .setPduSessionId(tunnelState.getPduSessionId()) |
| .build(); // underlying n/w is same |
| } |
| |
| private List<DataCallResponse> getCallList() { |
| List<DataCallResponse> dcList = new ArrayList<>(); |
| for (String key : mTunnelStateForApn.keySet()) { |
| DataCallResponse dcRsp = apnTunnelStateToDataCallResponse(key); |
| if (dcRsp != null) { |
| Log.d(SUB_TAG, "Apn: " + key + "Link state: " + dcRsp.getLinkStatus()); |
| dcList.add(dcRsp); |
| } |
| } |
| return dcList; |
| } |
| |
| private void deliverCallback( |
| int callbackType, int result, DataServiceCallback callback, DataCallResponse rsp) { |
| if (callback == null) { |
| Log.d(SUB_TAG, "deliverCallback: callback is null. callbackType:" + callbackType); |
| return; |
| } |
| Log.d( |
| SUB_TAG, |
| "Delivering callbackType:" |
| + callbackType |
| + " result:" |
| + result |
| + " rsp:" |
| + rsp); |
| switch (callbackType) { |
| case CALLBACK_TYPE_DEACTIVATE_DATACALL_COMPLETE: |
| callback.onDeactivateDataCallComplete(result); |
| // always update current datacalllist |
| notifyDataCallListChanged(getCallList()); |
| break; |
| |
| case CALLBACK_TYPE_SETUP_DATACALL_COMPLETE: |
| if (result == DataServiceCallback.RESULT_SUCCESS && rsp == null) { |
| Log.d(SUB_TAG, "Warning: null rsp for success case"); |
| } |
| callback.onSetupDataCallComplete(result, rsp); |
| // always update current datacalllist |
| notifyDataCallListChanged(getCallList()); |
| break; |
| |
| case CALLBACK_TYPE_GET_DATACALL_LIST_COMPLETE: |
| callback.onRequestDataCallListComplete(result, getCallList()); |
| // TODO: add code for the rest of the cases |
| } |
| } |
| |
| /** |
| * Setup a data connection. |
| * |
| * @param accessNetworkType Access network type that the data call will be established on. |
| * Must be one of {@link android.telephony.AccessNetworkConstants.AccessNetworkType}. |
| * @param dataProfile Data profile used for data call setup. See {@link DataProfile} |
| * @param isRoaming True if the device is data roaming. |
| * @param allowRoaming True if data roaming is allowed by the user. |
| * @param reason The reason for data setup. Must be {@link #REQUEST_REASON_NORMAL} or {@link |
| * #REQUEST_REASON_HANDOVER}. |
| * @param linkProperties If {@code reason} is {@link #REQUEST_REASON_HANDOVER}, this is the |
| * link properties of the existing data connection, otherwise null. |
| * @param pduSessionId The pdu session id to be used for this data call. The standard range |
| * of values are 1-15 while 0 means no pdu session id was attached to this call. |
| * Reference: 3GPP TS 24.007 section 11.2.3.1b. |
| * @param sliceInfo The slice info related to this data call. |
| * @param trafficDescriptor TrafficDescriptor for which data connection needs to be |
| * established. It is used for URSP traffic matching as described in 3GPP TS 24.526 |
| * Section 4.2.2. It includes an optional DNN which, if present, must be used for |
| * traffic matching; it does not specify the end point to be used for the data call. |
| * @param matchAllRuleAllowed Indicates if using default match-all URSP rule for this |
| * request is allowed. If false, this request must not use the match-all URSP rule and |
| * if a non-match-all rule is not found (or if URSP rules are not available) then {@link |
| * DataCallResponse#getCause()} is {@link |
| * android.telephony.DataFailCause#MATCH_ALL_RULE_NOT_ALLOWED}. This is needed as some |
| * requests need to have a hard failure if the intention cannot be met, for example, a |
| * zero-rating slice. |
| * @param callback The result callback for this request. |
| */ |
| @Override |
| public void setupDataCall( |
| int accessNetworkType, |
| @NonNull DataProfile dataProfile, |
| boolean isRoaming, |
| boolean allowRoaming, |
| @SetupDataReason int reason, |
| @Nullable LinkProperties linkProperties, |
| @IntRange(from = 0, to = 15) int pduSessionId, |
| @Nullable NetworkSliceInfo sliceInfo, |
| @Nullable TrafficDescriptor trafficDescriptor, |
| boolean matchAllRuleAllowed, |
| @NonNull DataServiceCallback callback) { |
| |
| mProcessingStartTime = System.currentTimeMillis(); |
| Log.d( |
| SUB_TAG, |
| "Setup data call with network: " |
| + accessNetworkType |
| + ", reason: " |
| + requestReasonToString(reason) |
| + ", pduSessionId: " |
| + pduSessionId |
| + ", DataProfile: " |
| + dataProfile |
| + ", isRoaming:" |
| + isRoaming |
| + ", allowRoaming: " |
| + allowRoaming |
| + ", linkProperties: " |
| + linkProperties); |
| |
| SetupDataCallData setupDataCallData = |
| new SetupDataCallData( |
| accessNetworkType, |
| dataProfile, |
| isRoaming, |
| allowRoaming, |
| reason, |
| linkProperties, |
| pduSessionId, |
| sliceInfo, |
| trafficDescriptor, |
| matchAllRuleAllowed, |
| callback, |
| this); |
| |
| int networkTransport = -1; |
| if (sDefaultDataTransport == Transport.MOBILE) { |
| networkTransport = TRANSPORT_CELLULAR; |
| } else if (sDefaultDataTransport == Transport.WIFI) { |
| networkTransport = TRANSPORT_WIFI; |
| } |
| |
| if (dataProfile != null) { |
| ApnSetting apnSetting = dataProfile.getApnSetting(); |
| this.setMetricsAtom( |
| // ApnName |
| apnSetting != null ? apnSetting.getApnName() : "", |
| // ApnType |
| apnSetting != null ? apnSetting.getApnTypeBitmask() : ApnSetting.TYPE_NONE, |
| // IsHandover |
| (reason == DataService.REQUEST_REASON_HANDOVER), |
| // Source Rat |
| getCurrentCellularRat(), |
| // IsRoaming |
| isRoaming, |
| // Is Network Connected |
| sNetworkConnected, |
| // Transport Type |
| networkTransport); |
| } |
| |
| getIwlanDataServiceHandler() |
| .sendMessage( |
| getIwlanDataServiceHandler() |
| .obtainMessage(EVENT_SETUP_DATA_CALL, setupDataCallData)); |
| } |
| |
| /** |
| * Deactivate a data connection. The data service provider must implement this method to |
| * support data connection tear down. When completed or error, the service must invoke the |
| * provided callback to notify the platform. |
| * |
| * <p>Note: For handovers, in compliance with 3GPP specs (TS 23.402 clause 8.6.1, TS 23.502 |
| * clause 4.11.4.1), a {@link KEY_HANDOVER_TO_WWAN_RELEASE_DELAY_SECOND_INT} delay is |
| * implemented to allow the network to release the IKE tunnel. If the network fails to |
| * release it within this timeframe, the UE will take over the release process. |
| * |
| * @param cid Call id returned in the callback of {@link |
| * DataServiceProvider#setupDataCall(int, DataProfile, boolean, boolean, int, |
| * LinkProperties, DataServiceCallback)}. |
| * @param reason The reason for data deactivation. Must be {@link #REQUEST_REASON_NORMAL}, |
| * {@link #REQUEST_REASON_SHUTDOWN} or {@link #REQUEST_REASON_HANDOVER}. |
| * @param callback The result callback for this request. Null if the client does not care |
| */ |
| @Override |
| public void deactivateDataCall( |
| int cid, @DeactivateDataReason int reason, DataServiceCallback callback) { |
| Log.d( |
| SUB_TAG, |
| "Deactivate data call with reason: " |
| + requestReasonToString(reason) |
| + ", cid: " |
| + cid |
| + ", callback: " |
| + callback); |
| |
| boolean isRequestForHandoverToWWAN = (reason == REQUEST_REASON_HANDOVER); |
| |
| int delayTimeSeconds = 0; |
| if (isRequestForHandoverToWWAN) { |
| delayTimeSeconds = |
| IwlanCarrierConfig.getConfigInt( |
| mContext, |
| getSlotIndex(), |
| IwlanCarrierConfig.KEY_HANDOVER_TO_WWAN_RELEASE_DELAY_SECOND_INT); |
| } |
| |
| int event = |
| (delayTimeSeconds > 0) |
| ? EVENT_DEACTIVATE_DATA_CALL_WITH_DELAY |
| : EVENT_DEACTIVATE_DATA_CALL; |
| |
| DeactivateDataCallData deactivateDataCallData = |
| new DeactivateDataCallData(cid, reason, callback, this, delayTimeSeconds); |
| |
| getIwlanDataServiceHandler() |
| .obtainMessage(event, deactivateDataCallData) |
| .sendToTarget(); |
| } |
| |
| public void forceCloseTunnelsInDeactivatingState() { |
| for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { |
| TunnelState tunnelState = entry.getValue(); |
| if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGDOWN) { |
| getTunnelManager() |
| .closeTunnel( |
| entry.getKey(), |
| true /* forceClose */, |
| getIwlanTunnelCallback(), |
| getIwlanTunnelMetrics(), |
| BRINGDOWN_REASON_IN_DEACTIVATING_STATE); |
| } |
| } |
| } |
| |
| /** |
| * Closes all tunnels forcefully for a specified reason. |
| * |
| * @param reason The reason for closing the tunnel. Must be {@link |
| * EpdgTunnelManager.TunnelBringDownReason}. |
| */ |
| void forceCloseTunnels(@EpdgTunnelManager.TunnelBringDownReason int reason) { |
| for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { |
| getTunnelManager() |
| .closeTunnel( |
| entry.getKey(), |
| true /* forceClose */, |
| getIwlanTunnelCallback(), |
| getIwlanTunnelMetrics(), |
| reason); |
| } |
| } |
| |
| /** |
| * Get the active data call list. |
| * |
| * @param callback The result callback for this request. |
| */ |
| @Override |
| public void requestDataCallList(DataServiceCallback callback) { |
| getIwlanDataServiceHandler() |
| .sendMessage( |
| getIwlanDataServiceHandler() |
| .obtainMessage( |
| EVENT_DATA_CALL_LIST_REQUEST, |
| new DataCallRequestData( |
| callback, IwlanDataServiceProvider.this))); |
| } |
| |
| @VisibleForTesting |
| protected void setTunnelState( |
| DataProfile dataProfile, |
| DataServiceCallback callback, |
| int tunnelStatus, |
| TunnelLinkProperties linkProperties, |
| boolean isHandover, |
| int pduSessionId, |
| boolean isImsOrEmergency, |
| boolean isDataCallSetupWithN1) { |
| TunnelState tunnelState = new TunnelState(callback); |
| tunnelState.setState(tunnelStatus); |
| tunnelState.setProtocolType(dataProfile.getApnSetting().getProtocol()); |
| tunnelState.setTunnelLinkProperties(linkProperties); |
| tunnelState.setIsHandover(isHandover); |
| tunnelState.setPduSessionId(pduSessionId); |
| tunnelState.setIsImsOrEmergency(isImsOrEmergency); |
| tunnelState.setIsDataCallWithN1(isDataCallSetupWithN1); |
| mTunnelStateForApn.put(dataProfile.getApnSetting().getApnName(), tunnelState); |
| } |
| |
| @VisibleForTesting |
| void setMetricsAtom( |
| String apnName, |
| int apntype, |
| boolean isHandover, |
| int sourceRat, |
| boolean isRoaming, |
| boolean isNetworkConnected, |
| int transportType) { |
| MetricsAtom metricsAtom = new MetricsAtom(); |
| metricsAtom.setApnType(apntype); |
| metricsAtom.setIsHandover(isHandover); |
| metricsAtom.setSourceRat(sourceRat); |
| metricsAtom.setIsCellularRoaming(isRoaming); |
| metricsAtom.setIsNetworkConnected(isNetworkConnected); |
| metricsAtom.setTransportType(transportType); |
| mMetricsAtomForApn.put(apnName, metricsAtom); |
| } |
| |
| @VisibleForTesting |
| @Nullable |
| public MetricsAtom getMetricsAtomByApn(String apnName) { |
| return mMetricsAtomForApn.get(apnName); |
| } |
| |
| @VisibleForTesting |
| public IwlanTunnelCallback getIwlanTunnelCallback() { |
| return mIwlanTunnelCallback; |
| } |
| |
| @VisibleForTesting |
| public IwlanTunnelMetricsImpl getIwlanTunnelMetrics() { |
| return mIwlanTunnelMetrics; |
| } |
| |
| @VisibleForTesting |
| IwlanDataTunnelStats getTunnelStats() { |
| return mTunnelStats; |
| } |
| |
| private void updateNetwork( |
| @Nullable Network network, @Nullable LinkProperties linkProperties) { |
| if (mIwlanDataService.isNetworkConnected( |
| isActiveDataOnOtherSub(getSlotIndex()), |
| IwlanHelper.isCrossSimCallingEnabled(mContext, getSlotIndex()))) { |
| getTunnelManager().updateNetwork(network, linkProperties); |
| } |
| |
| if (Objects.equals(network, sNetwork)) { |
| return; |
| } |
| for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { |
| TunnelState tunnelState = entry.getValue(); |
| if (tunnelState.getState() == TunnelState.TUNNEL_IN_BRINGUP) { |
| // force close tunnels in bringup since IKE lib only supports |
| // updating network for tunnels that are already up. |
| // This may not result in actual closing of Ike Session since |
| // epdg selection may not be complete yet. |
| tunnelState.setState(TunnelState.TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP); |
| getTunnelManager() |
| .closeTunnel( |
| entry.getKey(), |
| true /* forceClose */, |
| getIwlanTunnelCallback(), |
| getIwlanTunnelMetrics(), |
| BRINGDOWN_REASON_NETWORK_UPDATE_WHEN_TUNNEL_IN_BRINGUP); |
| } |
| } |
| } |
| |
| private boolean isRegisteredCellInfoChanged(List<CellInfo> cellInfoList) { |
| for (CellInfo cellInfo : cellInfoList) { |
| if (!cellInfo.isRegistered()) { |
| continue; |
| } |
| |
| if (mCellInfo == null || mCellInfo != cellInfo) { |
| mCellInfo = cellInfo; |
| Log.d(TAG, " Update cached cellinfo"); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void dnsPrefetchCheck() { |
| boolean networkConnected = |
| mIwlanDataService.isNetworkConnected( |
| isActiveDataOnOtherSub(getSlotIndex()), |
| IwlanHelper.isCrossSimCallingEnabled(mContext, getSlotIndex())); |
| /* Check if we need to do prefecting */ |
| if (networkConnected |
| && mCarrierConfigReady |
| && mWfcEnabled |
| && mTunnelStateForApn.isEmpty()) { |
| |
| // Get roaming status |
| TelephonyManager telephonyManager = |
| mContext.getSystemService(TelephonyManager.class); |
| telephonyManager = |
| telephonyManager.createForSubscriptionId( |
| IwlanHelper.getSubId(mContext, getSlotIndex())); |
| boolean isRoaming = telephonyManager.isNetworkRoaming(); |
| Log.d(TAG, "Trigger EPDG prefetch. Roaming=" + isRoaming); |
| |
| prefetchEpdgServerList(mIwlanDataService.sNetwork, isRoaming); |
| } |
| } |
| |
| private void prefetchEpdgServerList(Network network, boolean isRoaming) { |
| mEpdgSelector.getValidatedServerList( |
| 0, |
| EpdgSelector.PROTO_FILTER_IPV4V6, |
| EpdgSelector.SYSTEM_PREFERRED, |
| isRoaming, |
| false, |
| network, |
| null); |
| mEpdgSelector.getValidatedServerList( |
| 0, |
| EpdgSelector.PROTO_FILTER_IPV4V6, |
| EpdgSelector.SYSTEM_PREFERRED, |
| isRoaming, |
| true, |
| network, |
| null); |
| } |
| |
| private int getCurrentCellularRat() { |
| TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); |
| telephonyManager = |
| telephonyManager.createForSubscriptionId( |
| IwlanHelper.getSubId(mContext, getSlotIndex())); |
| List<CellInfo> cellInfoList = telephonyManager.getAllCellInfo(); |
| if (cellInfoList == null) { |
| Log.e(TAG, "cellInfoList is NULL"); |
| return 0; |
| } |
| |
| for (CellInfo cellInfo : cellInfoList) { |
| if (!cellInfo.isRegistered()) { |
| continue; |
| } |
| if (cellInfo instanceof CellInfoGsm) { |
| return TelephonyManager.NETWORK_TYPE_GSM; |
| } else if (cellInfo instanceof CellInfoWcdma) { |
| return TelephonyManager.NETWORK_TYPE_UMTS; |
| } else if (cellInfo instanceof CellInfoLte) { |
| return TelephonyManager.NETWORK_TYPE_LTE; |
| } else if (cellInfo instanceof CellInfoNr) { |
| return TelephonyManager.NETWORK_TYPE_NR; |
| } |
| } |
| return TelephonyManager.NETWORK_TYPE_UNKNOWN; |
| } |
| |
| /* Determines if this subscription is in an active call */ |
| private boolean isOnCall() { |
| return mCallState != TelephonyManager.CALL_STATE_IDLE; |
| } |
| |
| /** |
| * IMS and Emergency are not allowed to retry with initial attach during call to keep call |
| * continuity. Other APNs like XCAP and MMS are allowed to retry with initial attach |
| * regardless of the call state. |
| */ |
| private boolean shouldRetryWithInitialAttachForHandoverRequest( |
| String apn, TunnelState tunnelState) { |
| boolean isOnImsOrEmergencyCall = tunnelState.getIsImsOrEmergency() && isOnCall(); |
| return tunnelState.getIsHandover() |
| && !isOnImsOrEmergencyCall |
| && ErrorPolicyManager.getInstance(mContext, getSlotIndex()) |
| .shouldRetryWithInitialAttach(apn); |
| } |
| |
| /** |
| * Called when the instance of data service is destroyed (e.g. got unbind or binder died) or |
| * when the data service provider is removed. |
| */ |
| @Override |
| public void close() { |
| // TODO: call epdgtunnelmanager.releaseInstance or equivalent |
| mIwlanDataService.removeDataServiceProvider(this); |
| IwlanEventListener iwlanEventListener = |
| IwlanEventListener.getInstance(mContext, getSlotIndex()); |
| iwlanEventListener.removeEventListener(getIwlanDataServiceHandler()); |
| iwlanEventListener.unregisterContentObserver(); |
| } |
| |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("---- IwlanDataServiceProvider[" + getSlotIndex() + "] ----"); |
| boolean isDDS = IwlanHelper.isDefaultDataSlot(mContext, getSlotIndex()); |
| boolean isCSTEnabled = IwlanHelper.isCrossSimCallingEnabled(mContext, getSlotIndex()); |
| pw.println( |
| "isDefaultDataSlot: " |
| + isDDS |
| + "subID: " |
| + IwlanHelper.getSubId(mContext, getSlotIndex()) |
| + " mConnectedDataSub: " |
| + mConnectedDataSub |
| + " isCrossSimEnabled: " |
| + isCSTEnabled); |
| pw.println( |
| "isNetworkConnected: " |
| + isNetworkConnected( |
| isActiveDataOnOtherSub(getSlotIndex()), isCSTEnabled) |
| + " Wfc enabled: " |
| + mWfcEnabled); |
| for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { |
| pw.println("Tunnel state for APN: " + entry.getKey()); |
| pw.println(entry.getValue()); |
| } |
| pw.println(mTunnelStats); |
| EpdgTunnelManager.getInstance(mContext, getSlotIndex()).dump(pw); |
| ErrorPolicyManager.getInstance(mContext, getSlotIndex()).dump(pw); |
| pw.println("-------------------------------------"); |
| } |
| |
| @VisibleForTesting |
| public void setCalendar(Calendar c) { |
| mCalendar = c; |
| } |
| |
| private boolean isPdnReestablishNeededOnIdleN1Update() { |
| return isN1ModeSupported() && (needIncludeN1ModeCapability() != mIs5GEnabledOnUi); |
| } |
| |
| private void disconnectPdnForN1ModeUpdate() { |
| if (hasActiveOrInitiatingDataCall()) { |
| forceCloseTunnels( |
| mIs5GEnabledOnUi |
| ? EpdgTunnelManager.BRINGDOWN_REASON_ENABLE_N1_MODE |
| : EpdgTunnelManager.BRINGDOWN_REASON_DISABLE_N1_MODE); |
| } |
| } |
| |
| private boolean hasActiveOrInitiatingDataCall() { |
| return mTunnelStateForApn.values().stream() |
| .anyMatch( |
| tunnelState -> |
| tunnelState.getState() == TunnelState.TUNNEL_UP |
| || tunnelState.getState() |
| == TunnelState.TUNNEL_IN_BRINGUP); |
| } |
| |
| // TODO(b/309867756): Include N1_MODE_CAPABILITY inclusion status in metrics. |
| private boolean needIncludeN1ModeCapability() { |
| if (!IwlanCarrierConfig.getConfigBoolean( |
| mContext, |
| getSlotIndex(), |
| IwlanCarrierConfig.KEY_UPDATE_N1_MODE_ON_UI_CHANGE_BOOL)) { |
| return isN1ModeSupported(); |
| } |
| if (!isN1ModeSupported()) { |
| return false; |
| } |
| // Maintain uniform N1_MODE_CAPABILITY Notify inclusion for all PDNs. |
| // Initiate PDN with current N1 inclusion in tunnel_up or tunnel_in_bringup states; |
| // otherwise, use UI settings. |
| return hasActiveOrInitiatingDataCall() ? isDataCallSetupWithN1() : mIs5GEnabledOnUi; |
| } |
| |
| private boolean isDataCallSetupWithN1() { |
| return mTunnelStateForApn.values().stream().anyMatch(TunnelState::getIsDataCallWithN1); |
| } |
| |
| protected boolean isN1ModeSupported() { |
| int[] nrAvailabilities = |
| IwlanHelper.getConfig( |
| CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, |
| mContext, |
| getSlotIndex()); |
| Log.d( |
| TAG, |
| "KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY : " |
| + Arrays.toString(nrAvailabilities)); |
| return Arrays.stream(nrAvailabilities) |
| .anyMatch(k -> k == CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA); |
| } |
| } |
| |
| private final class IwlanDataServiceHandler extends Handler { |
| private final String TAG = IwlanDataServiceHandler.class.getSimpleName(); |
| |
| @Override |
| public void handleMessage(Message msg) { |
| Log.d(TAG, "msg.what = " + eventToString(msg.what)); |
| |
| String apnName; |
| IwlanDataServiceProvider iwlanDataServiceProvider; |
| IwlanDataServiceProvider.TunnelState tunnelState; |
| DataServiceCallback callback; |
| int reason; |
| int slotId; |
| int retryTimeMillis; |
| int errorCause; |
| MetricsAtom metricsAtom; |
| |
| switch (msg.what) { |
| case EVENT_TUNNEL_OPENED: |
| TunnelOpenedData tunnelOpenedData = (TunnelOpenedData) msg.obj; |
| iwlanDataServiceProvider = tunnelOpenedData.mIwlanDataServiceProvider; |
| apnName = tunnelOpenedData.mApnName; |
| TunnelLinkProperties tunnelLinkProperties = |
| tunnelOpenedData.mTunnelLinkProperties; |
| |
| tunnelState = iwlanDataServiceProvider.mTunnelStateForApn.get(apnName); |
| // tunnelstate should not be null, design violation. |
| // if its null, we should crash and debug. |
| tunnelState.setTunnelLinkProperties(tunnelLinkProperties); |
| tunnelState.setState(IwlanDataServiceProvider.TunnelState.TUNNEL_UP); |
| iwlanDataServiceProvider.mTunnelStats.reportTunnelSetupSuccess( |
| apnName, tunnelState); |
| |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| DataServiceCallback.RESULT_SUCCESS, |
| tunnelState.getDataServiceCallback(), |
| iwlanDataServiceProvider.apnTunnelStateToDataCallResponse(apnName)); |
| break; |
| |
| case EVENT_TUNNEL_CLOSED: |
| TunnelClosedData tunnelClosedData = (TunnelClosedData) msg.obj; |
| iwlanDataServiceProvider = tunnelClosedData.mIwlanDataServiceProvider; |
| apnName = tunnelClosedData.mApnName; |
| IwlanError iwlanError = tunnelClosedData.mIwlanError; |
| |
| tunnelState = iwlanDataServiceProvider.mTunnelStateForApn.get(apnName); |
| |
| if (tunnelState == null) { |
| // On a successful handover to EUTRAN, the NW may initiate an IKE DEL before |
| // the UE initiates a deactivateDataCall(). There may be a race condition |
| // where the deactivateDataCall() arrives immediately before |
| // IwlanDataService receives EVENT_TUNNEL_CLOSED (and clears TunnelState). |
| // Even though there is no tunnel, EpdgTunnelManager will still process the |
| // bringdown request and send back an onClosed() to ensure state coherence. |
| if (iwlanError.getErrorType() != IwlanError.TUNNEL_NOT_FOUND) { |
| Log.w( |
| TAG, |
| "Tunnel state does not exist! Unexpected IwlanError: " |
| + iwlanError); |
| } |
| break; |
| } |
| |
| if (tunnelState.hasPendingDeactivateDataCallData()) { |
| // Iwlan delays handling EVENT_DEACTIVATE_DATA_CALL to give the network time |
| // to release the PDN. This allows for immediate response to Telephony if |
| // the network releases the PDN before timeout. Otherwise, Telephony's PDN |
| // state waits for Iwlan, blocking further actions on this PDN. |
| resumePendingDeactivationIfExists( |
| tunnelState.getPendingDeactivateDataCallData()); |
| } |
| |
| iwlanDataServiceProvider.mTunnelStats.reportTunnelDown(apnName, tunnelState); |
| iwlanDataServiceProvider.mTunnelStateForApn.remove(apnName); |
| metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName); |
| |
| if (tunnelState.getState() |
| == IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP |
| || tunnelState.getState() |
| == IwlanDataServiceProvider.TunnelState |
| .TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP) { |
| DataCallResponse.Builder respBuilder = new DataCallResponse.Builder(); |
| respBuilder |
| .setId(apnName.hashCode()) |
| .setProtocolType(tunnelState.getRequestedProtocolType()); |
| |
| if (iwlanDataServiceProvider.shouldRetryWithInitialAttachForHandoverRequest( |
| apnName, tunnelState)) { |
| respBuilder.setHandoverFailureMode( |
| DataCallResponse |
| .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL); |
| metricsAtom.setHandoverFailureMode( |
| DataCallResponse |
| .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL); |
| } else if (tunnelState.getIsHandover()) { |
| respBuilder.setHandoverFailureMode( |
| DataCallResponse |
| .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER); |
| metricsAtom.setHandoverFailureMode( |
| DataCallResponse |
| .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER); |
| } |
| |
| errorCause = |
| ErrorPolicyManager.getInstance( |
| mContext, iwlanDataServiceProvider.getSlotIndex()) |
| .getDataFailCause(apnName); |
| if (errorCause != DataFailCause.NONE) { |
| respBuilder.setCause(errorCause); |
| metricsAtom.setDataCallFailCause(errorCause); |
| |
| retryTimeMillis = |
| (int) |
| ErrorPolicyManager.getInstance( |
| mContext, |
| iwlanDataServiceProvider.getSlotIndex()) |
| .getRemainingRetryTimeMs(apnName); |
| respBuilder.setRetryDurationMillis(retryTimeMillis); |
| metricsAtom.setRetryDurationMillis(retryTimeMillis); |
| } else { |
| // TODO(b/265215349): Use a different DataFailCause for scenario where |
| // tunnel in bringup is closed or force-closed without error. |
| respBuilder.setCause(DataFailCause.IWLAN_NETWORK_FAILURE); |
| metricsAtom.setDataCallFailCause(DataFailCause.IWLAN_NETWORK_FAILURE); |
| respBuilder.setRetryDurationMillis(5000); |
| metricsAtom.setRetryDurationMillis(5000); |
| } |
| |
| // Record setup result for the Metrics |
| metricsAtom.setSetupRequestResult(DataServiceCallback.RESULT_SUCCESS); |
| metricsAtom.setIwlanError(iwlanError.getErrorType()); |
| |
| metricsAtom.setIwlanErrorWrappedClassnameAndStack(iwlanError); |
| |
| metricsAtom.setTunnelState(tunnelState.getState()); |
| metricsAtom.setMessageId( |
| IwlanStatsLog.IWLAN_SETUP_DATA_CALL_RESULT_REPORTED); |
| |
| metricsAtom.setErrorCountOfSameCause( |
| ErrorPolicyManager.getInstance( |
| mContext, iwlanDataServiceProvider.getSlotIndex()) |
| .getLastErrorCountOfSameCause(apnName)); |
| |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| DataServiceCallback.RESULT_SUCCESS, |
| tunnelState.getDataServiceCallback(), |
| respBuilder.build()); |
| return; |
| } |
| |
| // iwlan service triggered teardown |
| if (tunnelState.getState() |
| == IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGDOWN) { |
| |
| // IO exception happens when IKE library fails to retransmit requests. |
| // This can happen for multiple reasons: |
| // 1. Network disconnection due to wifi off. |
| // 2. Epdg server does not respond. |
| // 3. Socket send/receive fails. |
| // Ignore this during tunnel bring down. |
| if (iwlanError.getErrorType() != IwlanError.NO_ERROR |
| && iwlanError.getErrorType() |
| != IwlanError.IKE_INTERNAL_IO_EXCEPTION) { |
| Log.e(TAG, "Unexpected error during tunnel bring down: " + iwlanError); |
| } |
| |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_DEACTIVATE_DATACALL_COMPLETE, |
| DataServiceCallback.RESULT_SUCCESS, |
| tunnelState.getDataServiceCallback(), |
| null); |
| |
| return; |
| } |
| |
| // just update list of data calls. No way to send error up |
| iwlanDataServiceProvider.notifyDataCallListChanged( |
| iwlanDataServiceProvider.getCallList()); |
| |
| // Report IwlanPdnDisconnectedReason due to the disconnection is neither for |
| // SETUP_DATA_CALL nor DEACTIVATE_DATA_CALL request. |
| metricsAtom.setDataCallFailCause( |
| ErrorPolicyManager.getInstance( |
| mContext, iwlanDataServiceProvider.getSlotIndex()) |
| .getDataFailCause(apnName)); |
| |
| WifiManager wifiManager = mContext.getSystemService(WifiManager.class); |
| if (wifiManager == null) { |
| Log.e(TAG, "Could not find wifiManager"); |
| return; |
| } |
| |
| WifiInfo wifiInfo = wifiManager.getConnectionInfo(); |
| if (wifiInfo == null) { |
| Log.e(TAG, "wifiInfo is null"); |
| return; |
| } |
| |
| metricsAtom.setWifiSignalValue(wifiInfo.getRssi()); |
| metricsAtom.setMessageId(IwlanStatsLog.IWLAN_PDN_DISCONNECTED_REASON_REPORTED); |
| break; |
| |
| case IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT: |
| iwlanDataServiceProvider = |
| (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); |
| |
| iwlanDataServiceProvider.mCarrierConfigReady = true; |
| iwlanDataServiceProvider.dnsPrefetchCheck(); |
| break; |
| |
| case IwlanEventListener.CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT: |
| iwlanDataServiceProvider = |
| (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); |
| |
| iwlanDataServiceProvider.mCarrierConfigReady = false; |
| break; |
| |
| case IwlanEventListener.WIFI_CALLING_ENABLE_EVENT: |
| iwlanDataServiceProvider = |
| (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); |
| |
| iwlanDataServiceProvider.mWfcEnabled = true; |
| iwlanDataServiceProvider.dnsPrefetchCheck(); |
| break; |
| |
| case IwlanEventListener.WIFI_CALLING_DISABLE_EVENT: |
| iwlanDataServiceProvider = |
| (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); |
| |
| iwlanDataServiceProvider.mWfcEnabled = false; |
| break; |
| |
| case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT: |
| iwlanDataServiceProvider = |
| (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); |
| iwlanDataServiceProvider.updateNetwork(sNetwork, sLinkProperties); |
| break; |
| |
| case IwlanEventListener.CELLINFO_CHANGED_EVENT: |
| List<CellInfo> cellInfolist = (List<CellInfo>) msg.obj; |
| iwlanDataServiceProvider = |
| (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); |
| |
| if (cellInfolist != null |
| && iwlanDataServiceProvider.isRegisteredCellInfoChanged(cellInfolist)) { |
| int[] addrResolutionMethods = |
| IwlanHelper.getConfig( |
| CarrierConfigManager.Iwlan |
| .KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, |
| mContext, |
| iwlanDataServiceProvider.getSlotIndex()); |
| for (int addrResolutionMethod : addrResolutionMethods) { |
| if (addrResolutionMethod |
| == CarrierConfigManager.Iwlan.EPDG_ADDRESS_CELLULAR_LOC) { |
| iwlanDataServiceProvider.dnsPrefetchCheck(); |
| } |
| } |
| } |
| break; |
| |
| case IwlanEventListener.CALL_STATE_CHANGED_EVENT: |
| iwlanDataServiceProvider = |
| (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); |
| |
| int previousCallState = iwlanDataServiceProvider.mCallState; |
| int currentCallState = iwlanDataServiceProvider.mCallState = msg.arg2; |
| |
| if (!IwlanCarrierConfig.getConfigBoolean( |
| mContext, |
| iwlanDataServiceProvider.getSlotIndex(), |
| IwlanCarrierConfig.KEY_UPDATE_N1_MODE_ON_UI_CHANGE_BOOL)) { |
| break; |
| } |
| |
| // Disconnect PDN if call ends and re-establishment needed. |
| if (previousCallState != currentCallState |
| && currentCallState == TelephonyManager.CALL_STATE_IDLE |
| && iwlanDataServiceProvider.isPdnReestablishNeededOnIdleN1Update()) { |
| iwlanDataServiceProvider.disconnectPdnForN1ModeUpdate(); |
| } |
| break; |
| |
| case IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT: |
| iwlanDataServiceProvider = |
| (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1); |
| if (!IwlanCarrierConfig.getConfigBoolean( |
| mContext, |
| iwlanDataServiceProvider.getSlotIndex(), |
| IwlanCarrierConfig.KEY_UPDATE_N1_MODE_ON_UI_CHANGE_BOOL)) { |
| break; |
| } |
| long allowedNetworkType = (long) msg.obj; |
| onPreferredNetworkTypeChanged(iwlanDataServiceProvider, allowedNetworkType); |
| break; |
| |
| case EVENT_SETUP_DATA_CALL: |
| SetupDataCallData setupDataCallData = (SetupDataCallData) msg.obj; |
| int accessNetworkType = setupDataCallData.mAccessNetworkType; |
| @NonNull DataProfile dataProfile = setupDataCallData.mDataProfile; |
| boolean isRoaming = setupDataCallData.mIsRoaming; |
| reason = setupDataCallData.mReason; |
| LinkProperties linkProperties = setupDataCallData.mLinkProperties; |
| @IntRange(from = 0, to = 15) |
| int pduSessionId = setupDataCallData.mPduSessionId; |
| callback = setupDataCallData.mCallback; |
| iwlanDataServiceProvider = setupDataCallData.mIwlanDataServiceProvider; |
| |
| if ((accessNetworkType != AccessNetworkType.IWLAN) |
| || (dataProfile == null) |
| || (dataProfile.getApnSetting() == null) |
| || (linkProperties == null |
| && reason == DataService.REQUEST_REASON_HANDOVER)) { |
| |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| DataServiceCallback.RESULT_ERROR_INVALID_ARG, |
| callback, |
| null); |
| return; |
| } |
| |
| slotId = iwlanDataServiceProvider.getSlotIndex(); |
| boolean isCSTEnabled = IwlanHelper.isCrossSimCallingEnabled(mContext, slotId); |
| boolean networkConnected = |
| isNetworkConnected(isActiveDataOnOtherSub(slotId), isCSTEnabled); |
| Log.d( |
| TAG + "[" + slotId + "]", |
| "isDds: " |
| + IwlanHelper.isDefaultDataSlot(mContext, slotId) |
| + ", isActiveDataOnOtherSub: " |
| + isActiveDataOnOtherSub(slotId) |
| + ", isCstEnabled: " |
| + isCSTEnabled |
| + ", transport: " |
| + sDefaultDataTransport); |
| |
| if (!networkConnected) { |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| 5 /* DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE |
| */, |
| callback, |
| null); |
| return; |
| } |
| |
| // Update Network & LinkProperties to EpdgTunnelManager |
| iwlanDataServiceProvider |
| .getTunnelManager() |
| .updateNetwork(sNetwork, sLinkProperties); |
| Log.d(TAG, "Update Network for SetupDataCall request"); |
| |
| tunnelState = |
| iwlanDataServiceProvider.mTunnelStateForApn.get( |
| dataProfile.getApnSetting().getApnName()); |
| |
| // Return the existing PDN if the pduSessionId is the same and the tunnel |
| // state is TUNNEL_UP. |
| if (tunnelState != null) { |
| if (tunnelState.getPduSessionId() == pduSessionId |
| && tunnelState.getState() |
| == IwlanDataServiceProvider.TunnelState.TUNNEL_UP) { |
| Log.w( |
| TAG + "[" + slotId + "]", |
| "The tunnel for " |
| + dataProfile.getApnSetting().getApnName() |
| + " already exists."); |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| DataServiceCallback.RESULT_SUCCESS, |
| callback, |
| iwlanDataServiceProvider.apnTunnelStateToDataCallResponse( |
| dataProfile.getApnSetting().getApnName())); |
| } else { |
| Log.e( |
| TAG + "[" + slotId + "]", |
| "Force close the existing PDN. pduSessionId = " |
| + tunnelState.getPduSessionId() |
| + " Tunnel State = " |
| + tunnelState.getState()); |
| iwlanDataServiceProvider |
| .getTunnelManager() |
| .closeTunnel( |
| dataProfile.getApnSetting().getApnName(), |
| true /* forceClose */, |
| iwlanDataServiceProvider.getIwlanTunnelCallback(), |
| iwlanDataServiceProvider.getIwlanTunnelMetrics(), |
| BRINGDOWN_REASON_SERVICE_OUT_OF_SYNC); |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| 5 /* DataServiceCallback |
| .RESULT_ERROR_TEMPORARILY_UNAVAILABLE */, |
| callback, |
| null); |
| } |
| return; |
| } |
| |
| int apnTypeBitmask = dataProfile.getApnSetting().getApnTypeBitmask(); |
| boolean isIms = hasApnTypes(apnTypeBitmask, ApnSetting.TYPE_IMS); |
| boolean isEmergency = hasApnTypes(apnTypeBitmask, ApnSetting.TYPE_EMERGENCY); |
| |
| boolean isDataCallSetupWithN1 = |
| iwlanDataServiceProvider.needIncludeN1ModeCapability(); |
| |
| // Override N1_MODE_CAPABILITY exclusion only for Emergency PDN due to carrier |
| // network limitations |
| if (IwlanCarrierConfig.getConfigBoolean( |
| mContext, |
| slotId, |
| IwlanCarrierConfig |
| .KEY_N1_MODE_EXCLUSION_FOR_EMERGENCY_SESSION_BOOL) |
| && isEmergency) { |
| isDataCallSetupWithN1 = false; |
| } |
| |
| TunnelSetupRequest.Builder tunnelReqBuilder = |
| TunnelSetupRequest.builder() |
| .setApnName(dataProfile.getApnSetting().getApnName()) |
| .setIsRoaming(isRoaming) |
| .setPduSessionId( |
| isDataCallSetupWithN1 |
| ? pduSessionId |
| : PDU_SESSION_ID_UNSET) |
| .setApnIpProtocol( |
| isRoaming |
| ? dataProfile |
| .getApnSetting() |
| .getRoamingProtocol() |
| : dataProfile.getApnSetting().getProtocol()) |
| .setRequestPcscf(isIms || isEmergency) |
| .setIsEmergency(isEmergency); |
| |
| if (reason == DataService.REQUEST_REASON_HANDOVER) { |
| // for now assume that, at max, only one address of eachtype (v4/v6). |
| // TODO: Check if multiple ips can be sent in ike tunnel setup |
| for (LinkAddress lAddr : linkProperties.getLinkAddresses()) { |
| if (lAddr.isIpv4()) { |
| tunnelReqBuilder.setSrcIpv4Address(lAddr.getAddress()); |
| } else if (lAddr.isIpv6()) { |
| tunnelReqBuilder.setSrcIpv6Address(lAddr.getAddress()); |
| tunnelReqBuilder.setSrcIpv6AddressPrefixLength( |
| lAddr.getPrefixLength()); |
| } |
| } |
| } |
| |
| iwlanDataServiceProvider.setTunnelState( |
| dataProfile, |
| callback, |
| IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP, |
| null, |
| (reason == DataService.REQUEST_REASON_HANDOVER), |
| pduSessionId, |
| isIms || isEmergency, |
| isDataCallSetupWithN1); |
| |
| boolean result = |
| iwlanDataServiceProvider |
| .getTunnelManager() |
| .bringUpTunnel( |
| tunnelReqBuilder.build(), |
| iwlanDataServiceProvider.getIwlanTunnelCallback(), |
| iwlanDataServiceProvider.getIwlanTunnelMetrics()); |
| Log.d(TAG + "[" + slotId + "]", "bringup Tunnel with result:" + result); |
| if (!result) { |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| DataServiceCallback.RESULT_ERROR_INVALID_ARG, |
| callback, |
| null); |
| return; |
| } |
| break; |
| |
| case EVENT_DEACTIVATE_DATA_CALL: |
| handleDeactivateDataCall((DeactivateDataCallData) msg.obj); |
| break; |
| |
| case EVENT_DEACTIVATE_DATA_CALL_WITH_DELAY: |
| handleDeactivateDataCallWithDelay((DeactivateDataCallData) msg.obj); |
| break; |
| |
| case EVENT_DATA_CALL_LIST_REQUEST: |
| DataCallRequestData dataCallRequestData = (DataCallRequestData) msg.obj; |
| callback = dataCallRequestData.mCallback; |
| iwlanDataServiceProvider = dataCallRequestData.mIwlanDataServiceProvider; |
| |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_GET_DATACALL_LIST_COMPLETE, |
| DataServiceCallback.RESULT_SUCCESS, |
| callback, |
| null); |
| break; |
| |
| case EVENT_FORCE_CLOSE_TUNNEL: |
| for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { |
| dp.forceCloseTunnels(EpdgTunnelManager.BRINGDOWN_REASON_UNKNOWN); |
| } |
| break; |
| |
| case EVENT_ADD_DATA_SERVICE_PROVIDER: |
| iwlanDataServiceProvider = (IwlanDataServiceProvider) msg.obj; |
| addIwlanDataServiceProvider(iwlanDataServiceProvider); |
| break; |
| |
| case EVENT_REMOVE_DATA_SERVICE_PROVIDER: |
| iwlanDataServiceProvider = (IwlanDataServiceProvider) msg.obj; |
| |
| slotId = iwlanDataServiceProvider.getSlotIndex(); |
| IwlanDataServiceProvider dsp = sIwlanDataServiceProviders.remove(slotId); |
| if (dsp == null) { |
| Log.w(TAG + "[" + slotId + "]", "No DataServiceProvider exists for slot!"); |
| } |
| |
| if (sIwlanDataServiceProviders.isEmpty()) { |
| deinitNetworkCallback(); |
| } |
| break; |
| |
| case EVENT_TUNNEL_OPENED_METRICS: |
| OnOpenedMetrics openedMetricsData = (OnOpenedMetrics) msg.obj; |
| iwlanDataServiceProvider = openedMetricsData.getIwlanDataServiceProvider(); |
| apnName = openedMetricsData.getApnName(); |
| |
| // Record setup result for the Metrics |
| metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName); |
| tunnelState = iwlanDataServiceProvider.mTunnelStateForApn.get(apnName); |
| metricsAtom.setSetupRequestResult(DataServiceCallback.RESULT_SUCCESS); |
| metricsAtom.setIwlanError(IwlanError.NO_ERROR); |
| metricsAtom.setDataCallFailCause(DataFailCause.NONE); |
| metricsAtom.setTunnelState(tunnelState.getState()); |
| metricsAtom.setHandoverFailureMode(-1); |
| metricsAtom.setRetryDurationMillis(0); |
| metricsAtom.setMessageId(IwlanStatsLog.IWLAN_SETUP_DATA_CALL_RESULT_REPORTED); |
| metricsAtom.setEpdgServerAddress(openedMetricsData.getEpdgServerAddress()); |
| metricsAtom.setProcessingDurationMillis( |
| (int) |
| (System.currentTimeMillis() |
| - iwlanDataServiceProvider.mProcessingStartTime)); |
| metricsAtom.setEpdgServerSelectionDurationMillis( |
| openedMetricsData.getEpdgServerSelectionDuration()); |
| metricsAtom.setIkeTunnelEstablishmentDurationMillis( |
| openedMetricsData.getIkeTunnelEstablishmentDuration()); |
| |
| metricsAtom.sendMetricsData(); |
| metricsAtom.setMessageId(metricsAtom.INVALID_MESSAGE_ID); |
| break; |
| |
| case EVENT_TUNNEL_CLOSED_METRICS: |
| OnClosedMetrics closedMetricsData = (OnClosedMetrics) msg.obj; |
| iwlanDataServiceProvider = closedMetricsData.getIwlanDataServiceProvider(); |
| apnName = closedMetricsData.getApnName(); |
| |
| metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName); |
| if (metricsAtom == null) { |
| Log.w(TAG, "EVENT_TUNNEL_CLOSED_METRICS: MetricsAtom is null!"); |
| break; |
| } |
| metricsAtom.setEpdgServerAddress(closedMetricsData.getEpdgServerAddress()); |
| metricsAtom.setProcessingDurationMillis( |
| iwlanDataServiceProvider.mProcessingStartTime > 0 |
| ? (int) |
| (System.currentTimeMillis() |
| - iwlanDataServiceProvider.mProcessingStartTime) |
| : 0); |
| metricsAtom.setEpdgServerSelectionDurationMillis( |
| closedMetricsData.getEpdgServerSelectionDuration()); |
| metricsAtom.setIkeTunnelEstablishmentDurationMillis( |
| closedMetricsData.getIkeTunnelEstablishmentDuration()); |
| |
| metricsAtom.sendMetricsData(); |
| metricsAtom.setMessageId(metricsAtom.INVALID_MESSAGE_ID); |
| iwlanDataServiceProvider.mMetricsAtomForApn.remove(apnName); |
| break; |
| |
| default: |
| throw new IllegalStateException("Unexpected value: " + msg.what); |
| } |
| } |
| |
| public void handleDeactivateDataCall(DeactivateDataCallData data) { |
| handleDeactivateDataCall(data, false); |
| } |
| |
| public void handleDeactivateDataCallWithDelay(DeactivateDataCallData data) { |
| handleDeactivateDataCall(data, true); |
| } |
| |
| public void handleDeactivateDataCall(DeactivateDataCallData data, boolean isWithDelay) { |
| IwlanDataServiceProvider serviceProvider = data.mIwlanDataServiceProvider; |
| String matchingApn = findMatchingApn(serviceProvider, data.mCid); |
| |
| if (matchingApn == null) { |
| deliverDeactivationError(serviceProvider, data.mCallback); |
| return; |
| } |
| |
| if (isWithDelay) { |
| Log.d(TAG, "Delaying deactivation for APN: " + matchingApn); |
| scheduleDelayedDeactivateDataCall(serviceProvider, data, matchingApn); |
| return; |
| } |
| Log.d(TAG, "Processing deactivation for APN: " + matchingApn); |
| processDeactivateDataCall(serviceProvider, data, matchingApn); |
| } |
| |
| private String findMatchingApn(IwlanDataServiceProvider serviceProvider, int cid) { |
| return serviceProvider.mTunnelStateForApn.keySet().stream() |
| .filter(apn -> apn.hashCode() == cid) |
| .findFirst() |
| .orElse(null); |
| } |
| |
| private void deliverDeactivationError( |
| IwlanDataServiceProvider serviceProvider, DataServiceCallback callback) { |
| serviceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_DEACTIVATE_DATACALL_COMPLETE, |
| DataServiceCallback.RESULT_ERROR_INVALID_ARG, |
| callback, |
| null); |
| } |
| |
| private void scheduleDelayedDeactivateDataCall( |
| IwlanDataServiceProvider serviceProvider, |
| DeactivateDataCallData data, |
| String matchingApn) { |
| IwlanDataServiceProvider.TunnelState tunnelState = |
| serviceProvider.mTunnelStateForApn.get(matchingApn); |
| tunnelState.setPendingDeactivateDataCallData(data); |
| Handler handler = getIwlanDataServiceHandler(); |
| handler.sendMessageDelayed( |
| handler.obtainMessage(EVENT_DEACTIVATE_DATA_CALL, data), |
| data.mDelayTimeSeconds * 1000L); |
| } |
| |
| private void processDeactivateDataCall( |
| IwlanDataServiceProvider serviceProvider, |
| DeactivateDataCallData data, |
| String matchingApn) { |
| int slotId = serviceProvider.getSlotIndex(); |
| boolean isNetworkLost = |
| !isNetworkConnected( |
| isActiveDataOnOtherSub(slotId), |
| IwlanHelper.isCrossSimCallingEnabled(mContext, slotId)); |
| boolean isHandoverSuccessful = (data.mReason == REQUEST_REASON_HANDOVER); |
| |
| IwlanDataServiceProvider.TunnelState tunnelState = |
| serviceProvider.mTunnelStateForApn.get(matchingApn); |
| tunnelState.setState(IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGDOWN); |
| tunnelState.setDataServiceCallback(data.mCallback); |
| |
| serviceProvider |
| .getTunnelManager() |
| .closeTunnel( |
| matchingApn, |
| isNetworkLost || isHandoverSuccessful, /* forceClose */ |
| serviceProvider.getIwlanTunnelCallback(), |
| serviceProvider.getIwlanTunnelMetrics(), |
| BRINGDOWN_REASON_DEACTIVATE_DATA_CALL); |
| } |
| |
| private void resumePendingDeactivationIfExists( |
| DeactivateDataCallData deactivateDataCallData) { |
| Handler handler = getIwlanDataServiceHandler(); |
| if (handler.hasMessages(EVENT_DEACTIVATE_DATA_CALL, deactivateDataCallData)) { |
| // Remove any existing deactivation messages and request a new one in the front |
| handler.removeMessages(EVENT_DEACTIVATE_DATA_CALL, deactivateDataCallData); |
| handler.sendMessageAtFrontOfQueue( |
| handler.obtainMessage(EVENT_DEACTIVATE_DATA_CALL, deactivateDataCallData)); |
| } |
| } |
| |
| IwlanDataServiceHandler(Looper looper) { |
| super(looper); |
| } |
| } |
| |
| private static final class TunnelOpenedData { |
| final String mApnName; |
| final TunnelLinkProperties mTunnelLinkProperties; |
| final IwlanDataServiceProvider mIwlanDataServiceProvider; |
| |
| private TunnelOpenedData( |
| String apnName, |
| TunnelLinkProperties tunnelLinkProperties, |
| IwlanDataServiceProvider dsp) { |
| mApnName = apnName; |
| mTunnelLinkProperties = tunnelLinkProperties; |
| mIwlanDataServiceProvider = dsp; |
| } |
| } |
| |
| private static final class TunnelClosedData { |
| final String mApnName; |
| final IwlanError mIwlanError; |
| final IwlanDataServiceProvider mIwlanDataServiceProvider; |
| |
| private TunnelClosedData( |
| String apnName, IwlanError iwlanError, IwlanDataServiceProvider dsp) { |
| mApnName = apnName; |
| mIwlanError = iwlanError; |
| mIwlanDataServiceProvider = dsp; |
| } |
| } |
| |
| private static final class SetupDataCallData { |
| final int mAccessNetworkType; |
| @NonNull final DataProfile mDataProfile; |
| final boolean mIsRoaming; |
| final boolean mAllowRoaming; |
| final int mReason; |
| @Nullable final LinkProperties mLinkProperties; |
| |
| @IntRange(from = 0, to = 15) |
| final int mPduSessionId; |
| |
| @Nullable final NetworkSliceInfo mSliceInfo; |
| @Nullable final TrafficDescriptor mTrafficDescriptor; |
| final boolean mMatchAllRuleAllowed; |
| @NonNull final DataServiceCallback mCallback; |
| final IwlanDataServiceProvider mIwlanDataServiceProvider; |
| |
| private SetupDataCallData( |
| int accessNetworkType, |
| DataProfile dataProfile, |
| boolean isRoaming, |
| boolean allowRoaming, |
| int reason, |
| LinkProperties linkProperties, |
| int pduSessionId, |
| NetworkSliceInfo sliceInfo, |
| TrafficDescriptor trafficDescriptor, |
| boolean matchAllRuleAllowed, |
| DataServiceCallback callback, |
| IwlanDataServiceProvider dsp) { |
| mAccessNetworkType = accessNetworkType; |
| mDataProfile = dataProfile; |
| mIsRoaming = isRoaming; |
| mAllowRoaming = allowRoaming; |
| mReason = reason; |
| mLinkProperties = linkProperties; |
| mPduSessionId = pduSessionId; |
| mSliceInfo = sliceInfo; |
| mTrafficDescriptor = trafficDescriptor; |
| mMatchAllRuleAllowed = matchAllRuleAllowed; |
| mCallback = callback; |
| mIwlanDataServiceProvider = dsp; |
| } |
| } |
| |
| private static final class DeactivateDataCallData { |
| final int mCid; |
| final int mReason; |
| final DataServiceCallback mCallback; |
| final IwlanDataServiceProvider mIwlanDataServiceProvider; |
| final int mDelayTimeSeconds; |
| |
| private DeactivateDataCallData( |
| int cid, |
| int reason, |
| DataServiceCallback callback, |
| IwlanDataServiceProvider dsp, |
| int delayTimeSeconds) { |
| mCid = cid; |
| mReason = reason; |
| mCallback = callback; |
| mIwlanDataServiceProvider = dsp; |
| mDelayTimeSeconds = delayTimeSeconds; |
| } |
| } |
| |
| private static final class DataCallRequestData { |
| final DataServiceCallback mCallback; |
| final IwlanDataServiceProvider mIwlanDataServiceProvider; |
| |
| private DataCallRequestData(DataServiceCallback callback, IwlanDataServiceProvider dsp) { |
| mCallback = callback; |
| mIwlanDataServiceProvider = dsp; |
| } |
| } |
| |
| static int getConnectedDataSub(NetworkCapabilities networkCapabilities) { |
| int connectedDataSub = INVALID_SUB_ID; |
| NetworkSpecifier specifier = networkCapabilities.getNetworkSpecifier(); |
| TransportInfo transportInfo = networkCapabilities.getTransportInfo(); |
| |
| if (specifier instanceof TelephonyNetworkSpecifier) { |
| connectedDataSub = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); |
| } else if (transportInfo instanceof VcnTransportInfo) { |
| connectedDataSub = ((VcnTransportInfo) transportInfo).getSubId(); |
| } |
| return connectedDataSub; |
| } |
| |
| static void setConnectedDataSub(int subId) { |
| mConnectedDataSub = subId; |
| } |
| |
| @VisibleForTesting |
| static boolean isActiveDataOnOtherSub(int slotId) { |
| int subId = IwlanHelper.getSubId(mContext, slotId); |
| return mConnectedDataSub != INVALID_SUB_ID && subId != mConnectedDataSub; |
| } |
| |
| @VisibleForTesting |
| static boolean isNetworkConnected(boolean isActiveDataOnOtherSub, boolean isCstEnabled) { |
| if (isActiveDataOnOtherSub && isCstEnabled) { |
| // For cross-SIM IWLAN (Transport.MOBILE), an active data PDN must be maintained on the |
| // other subscription. |
| if (sNetworkConnected && (sDefaultDataTransport != Transport.MOBILE)) { |
| Log.e(TAG, "Internet is on other slot, but default transport is not MOBILE!"); |
| } |
| return sNetworkConnected; |
| } else { |
| // For all other cases, only Transport.WIFI can be used. |
| return ((sDefaultDataTransport == Transport.WIFI) && sNetworkConnected); |
| } |
| } |
| |
| /* Note: this api should have valid transport if networkConnected==true */ |
| static void setNetworkConnected( |
| boolean networkConnected, @NonNull Network network, Transport transport) { |
| |
| boolean hasNetworkChanged = false; |
| boolean hasTransportChanged = false; |
| boolean hasNetworkConnectedChanged = false; |
| |
| if (sNetworkConnected == networkConnected |
| && network.equals(sNetwork) |
| && sDefaultDataTransport == transport) { |
| // Nothing changed |
| return; |
| } |
| |
| // safety check |
| if (networkConnected && transport == Transport.UNSPECIFIED_NETWORK) { |
| Log.e(TAG, "setNetworkConnected: Network connected but transport unspecified"); |
| return; |
| } |
| |
| if (!network.equals(sNetwork)) { |
| Log.e(TAG, "System default network changed from: " + sNetwork + " TO: " + network); |
| hasNetworkChanged = true; |
| } |
| |
| if (transport != sDefaultDataTransport) { |
| Log.d( |
| TAG, |
| "Transport was changed from " |
| + sDefaultDataTransport.name() |
| + " to " |
| + transport.name()); |
| hasTransportChanged = true; |
| } |
| |
| if (sNetworkConnected != networkConnected) { |
| Log.d( |
| TAG, |
| "Network connected state change from " |
| + sNetworkConnected |
| + " to " |
| + networkConnected); |
| hasNetworkConnectedChanged = true; |
| } |
| |
| sDefaultDataTransport = transport; |
| sNetworkConnected = networkConnected; |
| |
| if (networkConnected) { |
| if (hasTransportChanged) { |
| // Perform forceClose for tunnels in bringdown. |
| // let framework handle explicit teardown |
| for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { |
| dp.forceCloseTunnelsInDeactivatingState(); |
| } |
| } |
| |
| if (transport == Transport.WIFI && hasNetworkConnectedChanged) { |
| IwlanEventListener.onWifiConnected(mContext); |
| } |
| // only prefetch dns and updateNetwork if Network has changed |
| if (hasNetworkChanged) { |
| ConnectivityManager connectivityManager = |
| mContext.getSystemService(ConnectivityManager.class); |
| LinkProperties linkProperties = connectivityManager.getLinkProperties(network); |
| sLinkProperties = linkProperties; |
| for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { |
| dp.dnsPrefetchCheck(); |
| dp.updateNetwork(network, linkProperties); |
| } |
| IwlanHelper.updateCountryCodeWhenNetworkConnected(); |
| } |
| } else { |
| for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { |
| // once network is disconnected, even NAT KA offload fails |
| // But we should still let framework do an explicit teardown |
| // so as to not affect an ongoing handover |
| // only force close tunnels in bring down state |
| dp.forceCloseTunnelsInDeactivatingState(); |
| } |
| } |
| sNetwork = network; |
| } |
| |
| /** |
| * Get the DataServiceProvider associated with the slotId |
| * |
| * @param slotId slot index |
| * @return DataService.DataServiceProvider associated with the slot |
| */ |
| public static DataService.DataServiceProvider getDataServiceProvider(int slotId) { |
| return sIwlanDataServiceProviders.get(slotId); |
| } |
| |
| public static Context getContext() { |
| return mContext; |
| } |
| |
| @Override |
| public DataServiceProvider onCreateDataServiceProvider(int slotIndex) { |
| // TODO: validity check on slot index |
| Log.d(TAG, "Creating provider for " + slotIndex); |
| |
| if (mNetworkMonitorCallback == null) { |
| // start monitoring network and register for default network callback |
| ConnectivityManager connectivityManager = |
| mContext.getSystemService(ConnectivityManager.class); |
| mNetworkMonitorCallback = new IwlanNetworkMonitorCallback(); |
| if (connectivityManager != null) { |
| connectivityManager.registerSystemDefaultNetworkCallback( |
| mNetworkMonitorCallback, getIwlanDataServiceHandler()); |
| } |
| Log.d(TAG, "Registered with Connectivity Service"); |
| } |
| |
| IwlanDataServiceProvider dp = new IwlanDataServiceProvider(slotIndex, this); |
| |
| getIwlanDataServiceHandler() |
| .sendMessage( |
| getIwlanDataServiceHandler() |
| .obtainMessage(EVENT_ADD_DATA_SERVICE_PROVIDER, dp)); |
| return dp; |
| } |
| |
| public void removeDataServiceProvider(IwlanDataServiceProvider dp) { |
| getIwlanDataServiceHandler() |
| .sendMessage( |
| getIwlanDataServiceHandler() |
| .obtainMessage(EVENT_REMOVE_DATA_SERVICE_PROVIDER, dp)); |
| } |
| |
| @VisibleForTesting |
| void addIwlanDataServiceProvider(IwlanDataServiceProvider dp) { |
| int slotIndex = dp.getSlotIndex(); |
| if (sIwlanDataServiceProviders.containsKey(slotIndex)) { |
| throw new IllegalStateException( |
| "DataServiceProvider already exists for slot " + slotIndex); |
| } |
| sIwlanDataServiceProviders.put(slotIndex, dp); |
| } |
| |
| void deinitNetworkCallback() { |
| // deinit network related stuff |
| ConnectivityManager connectivityManager = |
| mContext.getSystemService(ConnectivityManager.class); |
| if (connectivityManager != null) { |
| connectivityManager.unregisterNetworkCallback(mNetworkMonitorCallback); |
| } |
| mNetworkMonitorCallback = null; |
| } |
| |
| boolean hasApnTypes(int apnTypeBitmask, int expectedApn) { |
| return (apnTypeBitmask & expectedApn) != 0; |
| } |
| |
| @VisibleForTesting |
| void setAppContext(Context appContext) { |
| mContext = appContext; |
| } |
| |
| @VisibleForTesting |
| IwlanNetworkMonitorCallback getNetworkMonitorCallback() { |
| return mNetworkMonitorCallback; |
| } |
| |
| @VisibleForTesting |
| @NonNull |
| Handler getIwlanDataServiceHandler() { |
| if (mIwlanDataServiceHandler == null) { |
| mIwlanDataServiceHandler = new IwlanDataServiceHandler(getLooper()); |
| } |
| return mIwlanDataServiceHandler; |
| } |
| |
| @VisibleForTesting |
| Looper getLooper() { |
| mIwlanDataServiceHandlerThread = new HandlerThread("IwlanDataServiceThread"); |
| mIwlanDataServiceHandlerThread.start(); |
| return mIwlanDataServiceHandlerThread.getLooper(); |
| } |
| |
| private static String eventToString(int event) { |
| switch (event) { |
| case EVENT_TUNNEL_OPENED: |
| return "EVENT_TUNNEL_OPENED"; |
| case EVENT_TUNNEL_CLOSED: |
| return "EVENT_TUNNEL_CLOSED"; |
| case EVENT_SETUP_DATA_CALL: |
| return "EVENT_SETUP_DATA_CALL"; |
| case EVENT_DEACTIVATE_DATA_CALL: |
| return "EVENT_DEACTIVATE_DATA_CALL"; |
| case EVENT_DATA_CALL_LIST_REQUEST: |
| return "EVENT_DATA_CALL_LIST_REQUEST"; |
| case EVENT_FORCE_CLOSE_TUNNEL: |
| return "EVENT_FORCE_CLOSE_TUNNEL"; |
| case EVENT_ADD_DATA_SERVICE_PROVIDER: |
| return "EVENT_ADD_DATA_SERVICE_PROVIDER"; |
| case EVENT_REMOVE_DATA_SERVICE_PROVIDER: |
| return "EVENT_REMOVE_DATA_SERVICE_PROVIDER"; |
| case IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT: |
| return "CARRIER_CONFIG_CHANGED_EVENT"; |
| case IwlanEventListener.CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT: |
| return "CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT"; |
| case IwlanEventListener.WIFI_CALLING_ENABLE_EVENT: |
| return "WIFI_CALLING_ENABLE_EVENT"; |
| case IwlanEventListener.WIFI_CALLING_DISABLE_EVENT: |
| return "WIFI_CALLING_DISABLE_EVENT"; |
| case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT: |
| return "CROSS_SIM_CALLING_ENABLE_EVENT"; |
| case IwlanEventListener.CELLINFO_CHANGED_EVENT: |
| return "CELLINFO_CHANGED_EVENT"; |
| case EVENT_TUNNEL_OPENED_METRICS: |
| return "EVENT_TUNNEL_OPENED_METRICS"; |
| case EVENT_TUNNEL_CLOSED_METRICS: |
| return "EVENT_TUNNEL_CLOSED_METRICS"; |
| case EVENT_DEACTIVATE_DATA_CALL_WITH_DELAY: |
| return "EVENT_DEACTIVATE_DATA_CALL_WITH_DELAY"; |
| case IwlanEventListener.CALL_STATE_CHANGED_EVENT: |
| return "CALL_STATE_CHANGED_EVENT"; |
| case IwlanEventListener.PREFERRED_NETWORK_TYPE_CHANGED_EVENT: |
| return "PREFERRED_NETWORK_TYPE_CHANGED_EVENT"; |
| default: |
| return "Unknown(" + event + ")"; |
| } |
| } |
| |
| private void initAllowedNetworkType() { |
| TelephonyManager mTelephonyManager = mContext.getSystemService(TelephonyManager.class); |
| mIs5GEnabledOnUi = |
| ((mTelephonyManager.getAllowedNetworkTypesBitmask() |
| & TelephonyManager.NETWORK_TYPE_BITMASK_NR) |
| != 0); |
| } |
| |
| private void onPreferredNetworkTypeChanged( |
| IwlanDataServiceProvider iwlanDataServiceProvider, long allowedNetworkType) { |
| boolean isCurrentUiEnable5G = |
| (allowedNetworkType & TelephonyManager.NETWORK_TYPE_BITMASK_NR) != 0; |
| boolean isPreviousUiEnable5G = mIs5GEnabledOnUi; |
| mIs5GEnabledOnUi = isCurrentUiEnable5G; |
| if (!iwlanDataServiceProvider.isN1ModeSupported()) { |
| return; |
| } |
| if (isPreviousUiEnable5G != isCurrentUiEnable5G) { |
| if (!iwlanDataServiceProvider.isOnCall()) { |
| iwlanDataServiceProvider.disconnectPdnForN1ModeUpdate(); |
| } |
| } |
| } |
| |
| @Override |
| public void onCreate() { |
| Context context = getApplicationContext().createAttributionContext(CONTEXT_ATTRIBUTION_TAG); |
| setAppContext(context); |
| IwlanBroadcastReceiver.startListening(mContext); |
| IwlanCarrierConfigChangeListener.startListening(mContext); |
| IwlanHelper.startCountryDetector(mContext); |
| initAllowedNetworkType(); |
| } |
| |
| @Override |
| public void onDestroy() { |
| IwlanCarrierConfigChangeListener.stopListening(mContext); |
| IwlanBroadcastReceiver.stopListening(mContext); |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| Log.d(TAG, "IwlanDataService onBind"); |
| return super.onBind(intent); |
| } |
| |
| @Override |
| public boolean onUnbind(Intent intent) { |
| Log.d(TAG, "IwlanDataService onUnbind"); |
| getIwlanDataServiceHandler() |
| .sendMessage(getIwlanDataServiceHandler().obtainMessage(EVENT_FORCE_CLOSE_TUNNEL)); |
| return super.onUnbind(intent); |
| } |
| |
| private String requestReasonToString(int reason) { |
| switch (reason) { |
| case DataService.REQUEST_REASON_UNKNOWN: |
| return "UNKNOWN"; |
| case DataService.REQUEST_REASON_NORMAL: |
| return "NORMAL"; |
| case DataService.REQUEST_REASON_SHUTDOWN: |
| return "SHUTDOWN"; |
| case DataService.REQUEST_REASON_HANDOVER: |
| return "HANDOVER"; |
| default: |
| return "UNKNOWN(" + reason + ")"; |
| } |
| } |
| |
| @Override |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| String transport = "UNSPECIFIED"; |
| if (sDefaultDataTransport == Transport.MOBILE) { |
| transport = "CELLULAR"; |
| } else if (sDefaultDataTransport == Transport.WIFI) { |
| transport = "WIFI"; |
| } |
| pw.println("Default transport: " + transport); |
| for (IwlanDataServiceProvider provider : sIwlanDataServiceProviders.values()) { |
| pw.println(); |
| provider.dump(fd, pw, args); |
| pw.println(); |
| pw.println(); |
| } |
| } |
| } |