| /* |
| * 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 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.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.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.Calendar; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.LongSummaryStatistics; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| public class IwlanDataService extends DataService { |
| |
| private static final String TAG = IwlanDataService.class.getSimpleName(); |
| private static Context mContext; |
| private IwlanNetworkMonitorCallback mNetworkMonitorCallback; |
| private static boolean sNetworkConnected = false; |
| private static Network sNetwork = 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; |
| |
| @VisibleForTesting |
| enum Transport { |
| UNSPECIFIED_NETWORK, |
| MOBILE, |
| WIFI; |
| } |
| |
| private static Transport sDefaultDataTransport = Transport.UNSPECIFIED_NETWORK; |
| |
| enum LinkProtocolType { |
| UNKNOWN, |
| IPV4, |
| IPV6, |
| IPV4V6; |
| } |
| |
| private static LinkProtocolType sLinkProtocolType = LinkProtocolType.UNKNOWN; |
| |
| // 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(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(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(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(Network network, LinkProperties linkProperties) { |
| Log.d(TAG, "onLinkPropertiesChanged: " + linkProperties); |
| if (isLinkProtocolTypeChanged(linkProperties)) { |
| for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { |
| dp.dnsPrefetchCheck(); |
| } |
| } |
| } |
| |
| /** Called when access to the specified network is blocked or unblocked. */ |
| @Override |
| public void onBlockedStatusChanged(Network network, boolean blocked) { |
| // TODO: check if we need to handle this |
| Log.d(TAG, "onBlockedStatusChanged: " + network + " BLOCKED:" + blocked); |
| } |
| |
| @Override |
| public void onCapabilitiesChanged( |
| Network network, 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 IwlanTunnelCallbackMetrics mIwlanTunnelCallbackMetrics; |
| private boolean mWfcEnabled = false; |
| private boolean mCarrierConfigReady = false; |
| private EpdgSelector mEpdgSelector; |
| private 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 Map<String, TunnelState> mTunnelStateForApn = new ConcurrentHashMap<>(); |
| private Map<String, MetricsAtom> mMetricsAtomForApn = new ConcurrentHashMap<>(); |
| |
| // 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 substract 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; |
| |
| public int getPduSessionId() { |
| return mPduSessionId; |
| } |
| |
| public void setPduSessionId(int mPduSessionId) { |
| this.mPduSessionId = mPduSessionId; |
| } |
| |
| public int getProtocolType() { |
| return mProtocolType; |
| } |
| |
| public int getLinkMtu() { |
| if ((sDefaultDataTransport == Transport.MOBILE) && sNetworkConnected) { |
| return LINK_MTU_CST; |
| } else { |
| return LINK_MTU; // TODO: need to substract tunnelling overhead |
| } |
| } |
| |
| 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; |
| } |
| |
| /** |
| * @param state (TunnelState.TUNNEL_DOWN|TUNNEL_UP|TUNNEL_DOWN) |
| */ |
| public void setState(int state) { |
| mState = state; |
| if (mState == TunnelState.TUNNEL_IN_BRINGUP) { |
| mBringUpStateTime = Calendar.getInstance().getTime(); |
| } |
| if (mState == TunnelState.TUNNEL_UP) { |
| mUpStateTime = Calendar.getInstance().getTime(); |
| } |
| } |
| |
| public void setIsHandover(boolean isHandover) { |
| mIsHandover = isHandover; |
| } |
| |
| public boolean getIsHandover() { |
| return mIsHandover; |
| } |
| |
| public Date getBringUpStateTime() { |
| return mBringUpStateTime; |
| } |
| |
| public Date getUpStateTime() { |
| return mUpStateTime; |
| } |
| |
| public boolean getIsImsOrEmergency() { |
| return mIsImsOrEmergency; |
| } |
| |
| public void setIsImsOrEmergency(boolean isImsOrEmergency) { |
| mIsImsOrEmergency = isImsOrEmergency; |
| } |
| |
| @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: " + mState + " " + tunnelState); |
| sb.append("\n\tTunnel state is in Handover: " + mIsHandover); |
| if (mBringUpStateTime != null) { |
| sb.append("\n\tTunnel bring up initiated at: " + mBringUpStateTime); |
| } else { |
| sb.append("\n\tPotential leak. Null mBringUpStateTime"); |
| } |
| if (mUpStateTime != null) { |
| sb.append("\n\tTunnel is up at: " + 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: " + tunnelUpTime); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| @VisibleForTesting |
| class IwlanTunnelCallback implements EpdgTunnelManager.TunnelCallback { |
| |
| DataServiceProvider mDataServiceProvider; |
| |
| public IwlanTunnelCallback(DataServiceProvider dsp) { |
| mDataServiceProvider = dsp; |
| } |
| |
| // TODO: full implementation |
| |
| public void onOpened(String apnName, TunnelLinkProperties linkProperties) { |
| Log.d( |
| SUB_TAG, |
| "Tunnel opened!. APN: " + apnName + "linkproperties: " + linkProperties); |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.obtainMessage( |
| EVENT_TUNNEL_OPENED, |
| new TunnelOpenedData( |
| apnName, linkProperties, IwlanDataServiceProvider.this))); |
| } |
| |
| 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. |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.obtainMessage( |
| EVENT_TUNNEL_CLOSED, |
| new TunnelClosedData( |
| apnName, error, IwlanDataServiceProvider.this))); |
| } |
| } |
| |
| @VisibleForTesting |
| class IwlanTunnelCallbackMetrics implements EpdgTunnelManager.TunnelCallbackMetrics { |
| |
| DataServiceProvider mDataServiceProvider; |
| |
| public IwlanTunnelCallbackMetrics(DataServiceProvider dsp) { |
| mDataServiceProvider = dsp; |
| } |
| |
| public void onOpened( |
| String apnName, |
| String epdgServerAddress, |
| int epdgServerSelectionDuration, |
| int ikeTunnelEstablishmentDuration) { |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.obtainMessage( |
| EVENT_TUNNEL_OPENED_METRICS, |
| new TunnelOpenedMetricsData( |
| apnName, |
| epdgServerAddress, |
| System.currentTimeMillis() - mProcessingStartTime, |
| epdgServerSelectionDuration, |
| ikeTunnelEstablishmentDuration, |
| IwlanDataServiceProvider.this))); |
| } |
| |
| public void onClosed( |
| String apnName, |
| String epdgServerAddress, |
| int epdgServerSelectionDuration, |
| int ikeTunnelEstablishmentDuration) { |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.obtainMessage( |
| EVENT_TUNNEL_CLOSED_METRICS, |
| new TunnelClosedMetricsData( |
| apnName, |
| epdgServerAddress, |
| mProcessingStartTime > 0 |
| ? System.currentTimeMillis() - mProcessingStartTime |
| : 0, |
| epdgServerSelectionDuration, |
| ikeTunnelEstablishmentDuration, |
| IwlanDataServiceProvider.this))); |
| } |
| } |
| |
| /** 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; |
| private final int APN_COUNT_MAX = 10; |
| |
| public IwlanDataTunnelStats() { |
| mStartTime = Calendar.getInstance().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 deactivate call 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 = Calendar.getInstance().getTime(); |
| 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() { |
| if (mTunnelSetupSuccessStats.size() >= APN_COUNT_MAX |
| || mTunnelSetupFailureCounts.size() >= APN_COUNT_MAX |
| || mUnsolTunnelDownCounts.size() >= APN_COUNT_MAX |
| || mTunnelUpStats.size() >= APN_COUNT_MAX) { |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("IwlanDataTunnelStats:"); |
| sb.append("\n\tmStartTime: " + mStartTime); |
| sb.append("\n\ttunnelSetupSuccessStats:"); |
| for (Map.Entry<String, LongSummaryStatistics> entry : |
| mTunnelSetupSuccessStats.entrySet()) { |
| sb.append("\n\t Apn: " + entry.getKey()); |
| sb.append("\n\t " + entry.getValue()); |
| } |
| sb.append("\n\ttunnelUpStats:"); |
| for (Map.Entry<String, LongSummaryStatistics> entry : mTunnelUpStats.entrySet()) { |
| sb.append("\n\t Apn: " + entry.getKey()); |
| sb.append("\n\t " + entry.getValue()); |
| } |
| |
| sb.append("\n\ttunnelSetupFailureCounts: "); |
| for (Map.Entry<String, Long> entry : mTunnelSetupFailureCounts.entrySet()) { |
| sb.append("\n\t Apn: " + entry.getKey()); |
| sb.append("\n\t counts: " + entry.getValue()); |
| } |
| sb.append("\n\tunsolTunnelDownCounts: "); |
| for (Map.Entry<String, Long> entry : mTunnelSetupFailureCounts.entrySet()) { |
| sb.append("\n\t Apn: " + entry.getKey()); |
| sb.append("\n\t counts: " + entry.getValue()); |
| } |
| sb.append("\n\tendTime: " + Calendar.getInstance().getTime()); |
| return sb.toString(); |
| } |
| |
| private void reset() { |
| mStartTime = Calendar.getInstance().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); |
| mIwlanTunnelCallbackMetrics = new IwlanTunnelCallbackMetrics(this); |
| mEpdgSelector = EpdgSelector.getSelectorInstance(mContext, slotIndex); |
| 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.CELLINFO_CHANGED_EVENT); |
| events.add(IwlanEventListener.CALL_STATE_CHANGED_EVENT); |
| IwlanEventListener.getInstance(mContext, slotIndex) |
| .addEventListener(events, mIwlanDataServiceHandler); |
| } |
| |
| @VisibleForTesting |
| 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(); |
| responseBuilder |
| .setId(apn.hashCode()) |
| .setProtocolType(tunnelState.getProtocolType()) |
| .setCause(DataFailCause.NONE); |
| |
| responseBuilder.setLinkStatus(DataCallResponse.LINK_STATUS_INACTIVE); |
| int state = tunnelState.getState(); |
| |
| if (state == TunnelState.TUNNEL_UP) { |
| responseBuilder.setLinkStatus(DataCallResponse.LINK_STATUS_ACTIVE); |
| } |
| |
| TunnelLinkProperties tunnelLinkProperties = tunnelState.getTunnelLinkProperties(); |
| if (tunnelLinkProperties == null) { |
| Log.d(TAG, "PDN with empty linkproperties. TunnelState : " + state); |
| return responseBuilder.build(); |
| } |
| |
| // 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(t -> t.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(t -> t.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, |
| 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 |
| + ", DataProfile: " |
| + dataProfile |
| + ", isRoaming:" |
| + isRoaming |
| + ", allowRoaming: " |
| + allowRoaming |
| + ", reason: " |
| + reason |
| + ", linkProperties: " |
| + linkProperties |
| + ", pduSessionId: " |
| + pduSessionId); |
| |
| 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) { |
| this.setMetricsAtom( |
| // ApnName |
| dataProfile.getApn(), |
| // ApnType |
| dataProfile.getSupportedApnTypesBitmask(), |
| // IsHandover |
| (reason == DataService.REQUEST_REASON_HANDOVER), |
| // Source Rat |
| getCurrentCellularRat(), |
| // IsRoaming |
| isRoaming, |
| // Is Network Connected |
| sNetworkConnected, |
| // Transport Type |
| networkTransport); |
| } |
| |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.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. |
| * |
| * @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, int reason, DataServiceCallback callback) { |
| Log.d( |
| SUB_TAG, |
| "Deactivate data call " |
| + " reason: " |
| + reason |
| + " cid: " |
| + cid |
| + "callback: " |
| + callback); |
| |
| DeactivateDataCallData deactivateDataCallData = |
| new DeactivateDataCallData(cid, reason, callback, this); |
| |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.obtainMessage( |
| EVENT_DEACTIVATE_DATA_CALL, deactivateDataCallData)); |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| void forceCloseTunnels() { |
| for (Map.Entry<String, TunnelState> entry : mTunnelStateForApn.entrySet()) { |
| getTunnelManager().closeTunnel(entry.getKey(), true); |
| } |
| } |
| |
| /** |
| * Get the active data call list. |
| * |
| * @param callback The result callback for this request. |
| */ |
| @Override |
| public void requestDataCallList(DataServiceCallback callback) { |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.obtainMessage( |
| EVENT_DATA_CALL_LIST_REQUEST, |
| new DataCallRequestData(callback, IwlanDataServiceProvider.this))); |
| } |
| |
| @VisibleForTesting |
| void setTunnelState( |
| DataProfile dataProfile, |
| DataServiceCallback callback, |
| int tunnelStatus, |
| TunnelLinkProperties linkProperties, |
| boolean isHandover, |
| int pduSessionId, |
| boolean isImsOrEmergency) { |
| TunnelState tunnelState = new TunnelState(callback); |
| tunnelState.setState(tunnelStatus); |
| tunnelState.setProtocolType(dataProfile.getProtocolType()); |
| tunnelState.setTunnelLinkProperties(linkProperties); |
| tunnelState.setIsHandover(isHandover); |
| tunnelState.setPduSessionId(pduSessionId); |
| tunnelState.setIsImsOrEmergency(isImsOrEmergency); |
| mTunnelStateForApn.put(dataProfile.getApn(), 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 |
| public IwlanTunnelCallback getIwlanTunnelCallback() { |
| return mIwlanTunnelCallback; |
| } |
| |
| @VisibleForTesting |
| public IwlanTunnelCallbackMetrics getIwlanTunnelCallbackMetrics() { |
| return mIwlanTunnelCallbackMetrics; |
| } |
| |
| @VisibleForTesting |
| IwlanDataTunnelStats getTunnelStats() { |
| return mTunnelStats; |
| } |
| |
| private void updateNetwork(Network network) { |
| if (network != null) { |
| 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); |
| } else { |
| if (mIwlanDataService.isNetworkConnected( |
| isActiveDataOnOtherSub(getSlotIndex()), |
| IwlanHelper.isCrossSimCallingEnabled(mContext, getSlotIndex()))) { |
| getTunnelManager().updateNetwork(network, entry.getKey()); |
| } |
| } |
| } |
| } |
| } |
| |
| 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 == true |
| && mCarrierConfigReady == true |
| && mWfcEnabled == true |
| && 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(mIwlanDataServiceHandler); |
| 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(fd, pw, args); |
| ErrorPolicyManager.getInstance(mContext, getSlotIndex()).dump(fd, pw, args); |
| pw.println("-------------------------------------"); |
| } |
| } |
| |
| 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); |
| |
| // Record setup result for the Metrics |
| metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.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); |
| |
| 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); |
| 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.getProtocolType()); |
| |
| 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); |
| } |
| |
| if (tunnelState.getState() |
| == IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP) { |
| errorCause = |
| ErrorPolicyManager.getInstance( |
| mContext, |
| iwlanDataServiceProvider.getSlotIndex()) |
| .getDataFailCause(apnName); |
| respBuilder.setCause(errorCause); |
| metricsAtom.setDataCallFailCause(errorCause); |
| |
| retryTimeMillis = |
| (int) |
| ErrorPolicyManager.getInstance( |
| mContext, |
| iwlanDataServiceProvider.getSlotIndex()) |
| .getCurrentRetryTimeMs(apnName); |
| respBuilder.setRetryDurationMillis(retryTimeMillis); |
| metricsAtom.setRetryDurationMillis(retryTimeMillis); |
| |
| } else if (tunnelState.getState() |
| == IwlanDataServiceProvider.TunnelState |
| .TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP) { |
| 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.setTunnelState(tunnelState.getState()); |
| metricsAtom.setMessageId( |
| IwlanStatsLog.IWLAN_SETUP_DATA_CALL_RESULT_REPORTED); |
| |
| 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.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); |
| |
| iwlanDataServiceProvider.mCallState = msg.arg2; |
| 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) |
| || (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 == false) { |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| 5 /* DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE |
| */, |
| callback, |
| null); |
| return; |
| } |
| |
| tunnelState = |
| iwlanDataServiceProvider.mTunnelStateForApn.get(dataProfile.getApn()); |
| |
| // 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.getApn() + " already exists."); |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| DataServiceCallback.RESULT_SUCCESS, |
| callback, |
| iwlanDataServiceProvider.apnTunnelStateToDataCallResponse( |
| dataProfile.getApn())); |
| return; |
| } else { |
| Log.e( |
| TAG + "[" + slotId + "]", |
| "Force close the existing PDN. pduSessionId = " |
| + tunnelState.getPduSessionId() |
| + " Tunnel State = " |
| + tunnelState.getState()); |
| iwlanDataServiceProvider |
| .getTunnelManager() |
| .closeTunnel(dataProfile.getApn(), true /* forceClose */); |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE, |
| 5 /* DataServiceCallback |
| .RESULT_ERROR_TEMPORARILY_UNAVAILABLE */, |
| callback, |
| null); |
| return; |
| } |
| } |
| |
| TunnelSetupRequest.Builder tunnelReqBuilder = |
| TunnelSetupRequest.builder() |
| .setApnName(dataProfile.getApn()) |
| .setNetwork(sNetwork) |
| .setIsRoaming(isRoaming) |
| .setPduSessionId(pduSessionId) |
| .setApnIpProtocol( |
| isRoaming |
| ? dataProfile.getRoamingProtocolType() |
| : dataProfile.getProtocolType()); |
| |
| 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()); |
| } |
| } |
| } |
| |
| int apnTypeBitmask = dataProfile.getSupportedApnTypesBitmask(); |
| boolean isIMS = hasApnTypes(apnTypeBitmask, ApnSetting.TYPE_IMS); |
| boolean isEmergency = hasApnTypes(apnTypeBitmask, ApnSetting.TYPE_EMERGENCY); |
| tunnelReqBuilder.setRequestPcscf(isIMS || isEmergency); |
| tunnelReqBuilder.setIsEmergency(isEmergency); |
| |
| iwlanDataServiceProvider.setTunnelState( |
| dataProfile, |
| callback, |
| IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP, |
| null, |
| (reason == DataService.REQUEST_REASON_HANDOVER), |
| pduSessionId, |
| isIMS || isEmergency); |
| |
| boolean result = |
| iwlanDataServiceProvider |
| .getTunnelManager() |
| .bringUpTunnel( |
| tunnelReqBuilder.build(), |
| iwlanDataServiceProvider.getIwlanTunnelCallback(), |
| iwlanDataServiceProvider |
| .getIwlanTunnelCallbackMetrics()); |
| 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: |
| DeactivateDataCallData deactivateDataCallData = |
| (DeactivateDataCallData) msg.obj; |
| iwlanDataServiceProvider = deactivateDataCallData.mIwlanDataServiceProvider; |
| callback = deactivateDataCallData.mCallback; |
| reason = deactivateDataCallData.mReason; |
| |
| int cid = deactivateDataCallData.mCid; |
| slotId = iwlanDataServiceProvider.getSlotIndex(); |
| boolean isNetworkLost = |
| !isNetworkConnected( |
| isActiveDataOnOtherSub(slotId), |
| IwlanHelper.isCrossSimCallingEnabled(mContext, slotId)); |
| boolean isHandOutSuccessful = (reason == REQUEST_REASON_HANDOVER); |
| |
| for (String apn : iwlanDataServiceProvider.mTunnelStateForApn.keySet()) { |
| if (apn.hashCode() == cid) { |
| // No need to check state since dataconnection in framework serializes |
| // setup and deactivate calls using callId/cid. |
| iwlanDataServiceProvider |
| .mTunnelStateForApn |
| .get(apn) |
| .setState( |
| IwlanDataServiceProvider.TunnelState |
| .TUNNEL_IN_BRINGDOWN); |
| iwlanDataServiceProvider |
| .mTunnelStateForApn |
| .get(apn) |
| .setDataServiceCallback(callback); |
| |
| // According to the handover procedure in 3GPP specifications (TS 23.402 |
| // clause 8.6.1 for S1; TS 23.502 clause 4.11.4.1 for N1), if the PDN is |
| // handed out to another RAT, the IKE tunnel over ePDG SHOULD be |
| // released by the network. Thus, UE just released the tunnel locally. |
| iwlanDataServiceProvider |
| .getTunnelManager() |
| .closeTunnel( |
| apn, |
| isNetworkLost || isHandOutSuccessful /* forceClose */); |
| return; |
| } |
| } |
| |
| iwlanDataServiceProvider.deliverCallback( |
| IwlanDataServiceProvider.CALLBACK_TYPE_DEACTIVATE_DATACALL_COMPLETE, |
| DataServiceCallback.RESULT_ERROR_INVALID_ARG, |
| callback, |
| null); |
| 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(); |
| } |
| 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: |
| TunnelOpenedMetricsData tunnelOpenedMetricsData = |
| (TunnelOpenedMetricsData) msg.obj; |
| iwlanDataServiceProvider = tunnelOpenedMetricsData.mIwlanDataServiceProvider; |
| apnName = tunnelOpenedMetricsData.mApnName; |
| |
| metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName); |
| metricsAtom.setEpdgServerAddress(tunnelOpenedMetricsData.mEpdgServerAddress); |
| metricsAtom.setProcessingDurationMillis( |
| tunnelOpenedMetricsData.mProcessingDuration); |
| metricsAtom.setEpdgServerSelectionDurationMillis( |
| tunnelOpenedMetricsData.mEpdgServerSelectionDuration); |
| metricsAtom.setIkeTunnelEstablishmentDurationMillis( |
| tunnelOpenedMetricsData.mIkeTunnelEstablishmentDuration); |
| |
| metricsAtom.sendMetricsData(); |
| break; |
| |
| case EVENT_TUNNEL_CLOSED_METRICS: |
| TunnelClosedMetricsData tunnelClosedMetricsData = |
| (TunnelClosedMetricsData) msg.obj; |
| iwlanDataServiceProvider = tunnelClosedMetricsData.mIwlanDataServiceProvider; |
| apnName = tunnelClosedMetricsData.mApnName; |
| |
| metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName); |
| metricsAtom.setEpdgServerAddress(tunnelClosedMetricsData.mEpdgServerAddress); |
| metricsAtom.setProcessingDurationMillis( |
| tunnelClosedMetricsData.mProcessingDuration); |
| metricsAtom.setEpdgServerSelectionDurationMillis( |
| tunnelClosedMetricsData.mEpdgServerSelectionDuration); |
| metricsAtom.setIkeTunnelEstablishmentDurationMillis( |
| tunnelClosedMetricsData.mIkeTunnelEstablishmentDuration); |
| |
| metricsAtom.sendMetricsData(); |
| iwlanDataServiceProvider.mMetricsAtomForApn.remove(apnName); |
| break; |
| |
| default: |
| throw new IllegalStateException("Unexpected value: " + msg.what); |
| } |
| } |
| |
| 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 TunnelOpenedMetricsData { |
| final String mApnName; |
| final IwlanDataServiceProvider mIwlanDataServiceProvider; |
| final String mEpdgServerAddress; |
| final int mProcessingDuration; |
| final int mEpdgServerSelectionDuration; |
| final int mIkeTunnelEstablishmentDuration; |
| |
| private TunnelOpenedMetricsData( |
| String apnName, |
| String epdgServerAddress, |
| long processingDuration, |
| int epdgServerSelectionDuration, |
| int ikeTunnelEstablishmentDuration, |
| IwlanDataServiceProvider dsp) { |
| mApnName = apnName; |
| mIwlanDataServiceProvider = dsp; |
| mEpdgServerAddress = epdgServerAddress; |
| mProcessingDuration = (int) processingDuration; |
| mEpdgServerSelectionDuration = epdgServerSelectionDuration; |
| mIkeTunnelEstablishmentDuration = ikeTunnelEstablishmentDuration; |
| } |
| } |
| |
| private static final class TunnelClosedMetricsData { |
| final String mApnName; |
| final IwlanDataServiceProvider mIwlanDataServiceProvider; |
| final String mEpdgServerAddress; |
| final int mProcessingDuration; |
| final int mEpdgServerSelectionDuration; |
| final int mIkeTunnelEstablishmentDuration; |
| |
| private TunnelClosedMetricsData( |
| String apnName, |
| String epdgServerAddress, |
| long processingDuration, |
| int epdgServerSelectionDuration, |
| int ikeTunnelEstablishmentDuration, |
| IwlanDataServiceProvider dsp) { |
| mApnName = apnName; |
| mIwlanDataServiceProvider = dsp; |
| mEpdgServerAddress = epdgServerAddress; |
| mProcessingDuration = (int) processingDuration; |
| mEpdgServerSelectionDuration = epdgServerSelectionDuration; |
| mIkeTunnelEstablishmentDuration = ikeTunnelEstablishmentDuration; |
| } |
| } |
| |
| 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; |
| |
| private DeactivateDataCallData( |
| int cid, int reason, DataServiceCallback callback, IwlanDataServiceProvider dsp) { |
| mCid = cid; |
| mReason = reason; |
| mCallback = callback; |
| mIwlanDataServiceProvider = dsp; |
| } |
| } |
| |
| 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 != null && specifier instanceof TelephonyNetworkSpecifier) { |
| connectedDataSub = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); |
| } else if (transportInfo != null && 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); |
| } |
| } |
| |
| @VisibleForTesting |
| /* Note: this api should have valid transport if networkConnected==true */ |
| static void setNetworkConnected( |
| boolean networkConnected, 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; |
| } |
| |
| sNetworkConnected = networkConnected; |
| sDefaultDataTransport = transport; |
| sNetwork = network; |
| if (!networkConnected) { |
| // reset link protocol type |
| sLinkProtocolType = LinkProtocolType.UNKNOWN; |
| } |
| |
| 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) { |
| for (IwlanDataServiceProvider dp : sIwlanDataServiceProviders.values()) { |
| dp.dnsPrefetchCheck(); |
| dp.updateNetwork(sNetwork); |
| } |
| 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(); |
| } |
| } |
| } |
| |
| static boolean isLinkProtocolTypeChanged(LinkProperties linkProperties) { |
| boolean hasIPV4 = false; |
| boolean hasIPV6 = false; |
| |
| LinkProtocolType linkProtocolType = null; |
| if (linkProperties != null) { |
| for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { |
| InetAddress inetaddr = linkAddress.getAddress(); |
| // skip linklocal and loopback addresses |
| if (!inetaddr.isLoopbackAddress() && !inetaddr.isLinkLocalAddress()) { |
| if (inetaddr instanceof Inet4Address) { |
| hasIPV4 = true; |
| } else if (inetaddr instanceof Inet6Address) { |
| hasIPV6 = true; |
| } |
| } |
| } |
| |
| if (hasIPV4 && hasIPV6) { |
| linkProtocolType = LinkProtocolType.IPV4V6; |
| } else if (hasIPV4) { |
| linkProtocolType = LinkProtocolType.IPV4; |
| } else if (hasIPV6) { |
| linkProtocolType = LinkProtocolType.IPV6; |
| } |
| |
| if (sLinkProtocolType != linkProtocolType) { |
| Log.d( |
| TAG, |
| "LinkProtocolType was changed from " |
| + sLinkProtocolType |
| + " to " |
| + linkProtocolType); |
| sLinkProtocolType = linkProtocolType; |
| return true; |
| } |
| return false; |
| } |
| Log.w(TAG, "linkProperties is NULL."); |
| return false; |
| } |
| |
| /** |
| * 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 (mIwlanDataServiceHandler == null) { |
| initHandler(); |
| } |
| |
| if (mNetworkMonitorCallback == null) { |
| // start monitoring network and register for default network callback |
| ConnectivityManager connectivityManager = |
| mContext.getSystemService(ConnectivityManager.class); |
| mNetworkMonitorCallback = new IwlanNetworkMonitorCallback(); |
| connectivityManager.registerSystemDefaultNetworkCallback( |
| mNetworkMonitorCallback, mIwlanDataServiceHandler); |
| Log.d(TAG, "Registered with Connectivity Service"); |
| } |
| |
| IwlanDataServiceProvider dp = new IwlanDataServiceProvider(slotIndex, this); |
| |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.obtainMessage(EVENT_ADD_DATA_SERVICE_PROVIDER, dp)); |
| return dp; |
| } |
| |
| public void removeDataServiceProvider(IwlanDataServiceProvider dp) { |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.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); |
| 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 |
| void initHandler() { |
| mIwlanDataServiceHandler = new IwlanDataServiceHandler(getLooper()); |
| } |
| |
| @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.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 IwlanEventListener.CALL_STATE_CHANGED_EVENT: |
| return "CALL_STATE_CHANGED_EVENT"; |
| default: |
| return "Unknown(" + event + ")"; |
| } |
| } |
| |
| @Override |
| public void onCreate() { |
| setAppContext(getApplicationContext()); |
| IwlanBroadcastReceiver.startListening(mContext); |
| IwlanHelper.startCountryDetector(mContext); |
| } |
| |
| @Override |
| public void onDestroy() { |
| IwlanBroadcastReceiver.stopListening(mContext); |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| Log.d(TAG, "Iwlanservice onBind"); |
| return super.onBind(intent); |
| } |
| |
| @Override |
| public boolean onUnbind(Intent intent) { |
| Log.d(TAG, "IwlanService onUnbind"); |
| mIwlanDataServiceHandler.sendMessage( |
| mIwlanDataServiceHandler.obtainMessage(EVENT_FORCE_CLOSE_TUNNEL)); |
| return super.onUnbind(intent); |
| } |
| |
| @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(); |
| } |
| } |
| } |