| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony.dataconnection; |
| |
| import android.annotation.IntDef; |
| import android.hardware.radio.V1_4.DataConnActiveStatus; |
| import android.net.LinkAddress; |
| import android.os.AsyncResult; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RegistrantList; |
| import android.telephony.AccessNetworkConstants; |
| import android.telephony.DataFailCause; |
| import android.telephony.data.DataCallResponse; |
| |
| import com.android.internal.telephony.DctConstants; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.dataconnection.DataConnection.UpdateLinkPropertyResult; |
| import com.android.internal.telephony.util.TelephonyUtils; |
| import com.android.net.module.util.LinkPropertiesUtils; |
| import com.android.net.module.util.LinkPropertiesUtils.CompareResult; |
| import com.android.net.module.util.NetUtils; |
| import com.android.telephony.Rlog; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * Data Connection Controller which is a package visible class and controls |
| * multiple data connections. For instance listening for unsolicited messages |
| * and then demultiplexing them to the appropriate DC. |
| */ |
| public class DcController extends Handler { |
| private static final boolean DBG = true; |
| private static final boolean VDBG = false; |
| |
| /** Physical link state unknown */ |
| public static final int PHYSICAL_LINK_UNKNOWN = 0; |
| |
| /** Physical link state inactive (i.e. RRC idle) */ |
| public static final int PHYSICAL_LINK_NOT_ACTIVE = 1; |
| |
| /** Physical link state active (i.e. RRC connected) */ |
| public static final int PHYSICAL_LINK_ACTIVE = 2; |
| |
| /** @hide */ |
| @IntDef(prefix = { "PHYSICAL_LINK_" }, value = { |
| PHYSICAL_LINK_UNKNOWN, |
| PHYSICAL_LINK_NOT_ACTIVE, |
| PHYSICAL_LINK_ACTIVE |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface PhysicalLinkState{} |
| |
| private final Phone mPhone; |
| private final DcTracker mDct; |
| private final String mTag; |
| private final DataServiceManager mDataServiceManager; |
| private final DcTesterDeactivateAll mDcTesterDeactivateAll; |
| |
| // package as its used by Testing code |
| // @GuardedBy("mDcListAll") |
| final ArrayList<DataConnection> mDcListAll = new ArrayList<>(); |
| // @GuardedBy("mDcListAll") |
| private final HashMap<Integer, DataConnection> mDcListActiveByCid = new HashMap<>(); |
| |
| /** |
| * Aggregated physical link state from all data connections. This reflects the device's RRC |
| * connection state. |
| * // TODO: Instead of tracking the RRC state here, we should make PhysicalChannelConfig work in |
| * S. |
| */ |
| private @PhysicalLinkState int mPhysicalLinkState = PHYSICAL_LINK_UNKNOWN; |
| |
| private RegistrantList mPhysicalLinkStateChangedRegistrants = new RegistrantList(); |
| |
| /** |
| * Constructor. |
| * |
| * @param name to be used for the Controller |
| * @param phone the phone associated with Dcc and Dct |
| * @param dct the DataConnectionTracker associated with Dcc |
| * @param dataServiceManager the data service manager that manages data services |
| * @param looper looper for this handler |
| */ |
| private DcController(String name, Phone phone, DcTracker dct, |
| DataServiceManager dataServiceManager, Looper looper) { |
| super(looper); |
| mPhone = phone; |
| mDct = dct; |
| mTag = name; |
| mDataServiceManager = dataServiceManager; |
| |
| mDcTesterDeactivateAll = (TelephonyUtils.IS_DEBUGGABLE) |
| ? new DcTesterDeactivateAll(mPhone, DcController.this, this) |
| : null; |
| |
| if (mPhone != null && mDataServiceManager.getTransportType() |
| == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { |
| mPhone.mCi.registerForRilConnected(this, |
| DataConnection.EVENT_RIL_CONNECTED, null); |
| } |
| |
| mDataServiceManager.registerForDataCallListChanged(this, |
| DataConnection.EVENT_DATA_STATE_CHANGED); |
| } |
| |
| public static DcController makeDcc(Phone phone, DcTracker dct, |
| DataServiceManager dataServiceManager, Looper looper, |
| String tagSuffix) { |
| return new DcController("Dcc" + tagSuffix, phone, dct, dataServiceManager, looper); |
| } |
| |
| void dispose() { |
| log("dispose"); |
| if (mPhone != null & mDataServiceManager.getTransportType() |
| == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { |
| mPhone.mCi.unregisterForRilConnected(this); |
| } |
| mDataServiceManager.unregisterForDataCallListChanged(this); |
| |
| if (mDcTesterDeactivateAll != null) { |
| mDcTesterDeactivateAll.dispose(); |
| } |
| } |
| |
| void addDc(DataConnection dc) { |
| synchronized (mDcListAll) { |
| mDcListAll.add(dc); |
| } |
| } |
| |
| void removeDc(DataConnection dc) { |
| synchronized (mDcListAll) { |
| mDcListActiveByCid.remove(dc.mCid); |
| mDcListAll.remove(dc); |
| } |
| } |
| |
| public void addActiveDcByCid(DataConnection dc) { |
| if (DBG && dc.mCid < 0) { |
| log("addActiveDcByCid dc.mCid < 0 dc=" + dc); |
| } |
| synchronized (mDcListAll) { |
| mDcListActiveByCid.put(dc.mCid, dc); |
| } |
| } |
| |
| DataConnection getActiveDcByCid(int cid) { |
| synchronized (mDcListAll) { |
| return mDcListActiveByCid.get(cid); |
| } |
| } |
| |
| void removeActiveDcByCid(DataConnection dc) { |
| synchronized (mDcListAll) { |
| DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid); |
| if (DBG && removedDc == null) { |
| log("removeActiveDcByCid removedDc=null dc=" + dc); |
| } |
| } |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| AsyncResult ar; |
| |
| switch (msg.what) { |
| case DataConnection.EVENT_RIL_CONNECTED: |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| if (DBG) { |
| log("EVENT_RIL_CONNECTED mRilVersion=" |
| + ar.result); |
| } |
| } else { |
| log("Unexpected exception on EVENT_RIL_CONNECTED"); |
| } |
| break; |
| |
| case DataConnection.EVENT_DATA_STATE_CHANGED: |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| onDataStateChanged((ArrayList<DataCallResponse>) ar.result); |
| } else { |
| log("EVENT_DATA_STATE_CHANGED: exception; likely radio not available, ignore"); |
| } |
| break; |
| } |
| } |
| |
| /** |
| * Process the new list of "known" Data Calls |
| * @param dcsList as sent by RIL_UNSOL_DATA_CALL_LIST_CHANGED |
| */ |
| private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) { |
| final HashMap<Integer, DataConnection> dcListActiveByCid; |
| synchronized (mDcListAll) { |
| dcListActiveByCid = new HashMap<>(mDcListActiveByCid); |
| } |
| |
| if (DBG) { |
| log("onDataStateChanged: dcsList=" + dcsList |
| + " dcListActiveByCid=" + dcListActiveByCid); |
| } |
| |
| // Create hashmap of cid to DataCallResponse |
| HashMap<Integer, DataCallResponse> dataCallResponseListByCid = |
| new HashMap<Integer, DataCallResponse>(); |
| for (DataCallResponse dcs : dcsList) { |
| dataCallResponseListByCid.put(dcs.getId(), dcs); |
| } |
| |
| // Add a DC that is active but not in the |
| // dcsList to the list of DC's to retry |
| ArrayList<DataConnection> dcsToRetry = new ArrayList<DataConnection>(); |
| for (DataConnection dc : dcListActiveByCid.values()) { |
| if (dataCallResponseListByCid.get(dc.mCid) == null) { |
| if (DBG) log("onDataStateChanged: add to retry dc=" + dc); |
| dcsToRetry.add(dc); |
| } |
| } |
| if (DBG) log("onDataStateChanged: dcsToRetry=" + dcsToRetry); |
| |
| // Find which connections have changed state and send a notification or cleanup |
| // and any that are in active need to be retried. |
| ArrayList<ApnContext> apnsToCleanup = new ArrayList<ApnContext>(); |
| |
| boolean isAnyDataCallDormant = false; |
| boolean isAnyDataCallActive = false; |
| |
| for (DataCallResponse newState : dcsList) { |
| |
| DataConnection dc = dcListActiveByCid.get(newState.getId()); |
| if (dc == null) { |
| // UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed. |
| loge("onDataStateChanged: no associated DC yet, ignore"); |
| continue; |
| } |
| |
| List<ApnContext> apnContexts = dc.getApnContexts(); |
| if (apnContexts.size() == 0) { |
| if (DBG) loge("onDataStateChanged: no connected apns, ignore"); |
| } else { |
| // Determine if the connection/apnContext should be cleaned up |
| // or just a notification should be sent out. |
| if (DBG) { |
| log("onDataStateChanged: Found ConnId=" + newState.getId() |
| + " newState=" + newState.toString()); |
| } |
| if (newState.getLinkStatus() == DataConnActiveStatus.INACTIVE) { |
| if (mDct.isCleanupRequired.get()) { |
| apnsToCleanup.addAll(apnContexts); |
| mDct.isCleanupRequired.set(false); |
| } else { |
| int failCause = DataFailCause.getFailCause(newState.getCause()); |
| if (DataFailCause.isRadioRestartFailure(mPhone.getContext(), failCause, |
| mPhone.getSubId())) { |
| if (DBG) { |
| log("onDataStateChanged: X restart radio, failCause=" |
| + failCause); |
| } |
| mDct.sendRestartRadio(); |
| } else if (mDct.isPermanentFailure(failCause)) { |
| if (DBG) { |
| log("onDataStateChanged: inactive, add to cleanup list. " |
| + "failCause=" + failCause); |
| } |
| apnsToCleanup.addAll(apnContexts); |
| } else { |
| if (DBG) { |
| log("onDataStateChanged: inactive, add to retry list. " |
| + "failCause=" + failCause); |
| } |
| dcsToRetry.add(dc); |
| } |
| } |
| } else { |
| // Update the pdu session id |
| dc.setPduSessionId(newState.getPduSessionId()); |
| |
| dc.updatePcscfAddr(newState); |
| |
| // Its active so update the DataConnections link properties |
| UpdateLinkPropertyResult result = dc.updateLinkProperty(newState); |
| if (result.oldLp.equals(result.newLp)) { |
| if (DBG) log("onDataStateChanged: no change"); |
| } else { |
| if (LinkPropertiesUtils.isIdenticalInterfaceName( |
| result.oldLp, result.newLp)) { |
| if (!LinkPropertiesUtils.isIdenticalDnses( |
| result.oldLp, result.newLp) |
| || !LinkPropertiesUtils.isIdenticalRoutes( |
| result.oldLp, result.newLp) |
| || !LinkPropertiesUtils.isIdenticalHttpProxy( |
| result.oldLp, result.newLp) |
| || !LinkPropertiesUtils.isIdenticalAddresses( |
| result.oldLp, result.newLp)) { |
| // If the same address type was removed and |
| // added we need to cleanup |
| CompareResult<LinkAddress> car = |
| LinkPropertiesUtils.compareAddresses(result.oldLp, |
| result.newLp); |
| if (DBG) { |
| log("onDataStateChanged: oldLp=" + result.oldLp |
| + " newLp=" + result.newLp + " car=" + car); |
| } |
| boolean needToClean = false; |
| for (LinkAddress added : car.added) { |
| for (LinkAddress removed : car.removed) { |
| if (NetUtils.addressTypeMatches( |
| removed.getAddress(), |
| added.getAddress())) { |
| needToClean = true; |
| break; |
| } |
| } |
| } |
| if (needToClean) { |
| if (DBG) { |
| log("onDataStateChanged: addr change," |
| + " cleanup apns=" + apnContexts |
| + " oldLp=" + result.oldLp |
| + " newLp=" + result.newLp); |
| } |
| apnsToCleanup.addAll(apnContexts); |
| } |
| } else { |
| if (DBG) { |
| log("onDataStateChanged: no changes"); |
| } |
| } |
| } else { |
| apnsToCleanup.addAll(apnContexts); |
| if (DBG) { |
| log("onDataStateChanged: interface change, cleanup apns=" |
| + apnContexts); |
| } |
| } |
| } |
| } |
| } |
| |
| if (newState.getLinkStatus() == DataConnActiveStatus.ACTIVE) { |
| isAnyDataCallActive = true; |
| } |
| if (newState.getLinkStatus() == DataConnActiveStatus.DORMANT) { |
| isAnyDataCallDormant = true; |
| } |
| } |
| |
| if (mDataServiceManager.getTransportType() |
| == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { |
| int physicalLinkState = isAnyDataCallActive |
| ? PHYSICAL_LINK_ACTIVE : PHYSICAL_LINK_NOT_ACTIVE; |
| if (mPhysicalLinkState != physicalLinkState) { |
| mPhysicalLinkState = physicalLinkState; |
| mPhysicalLinkStateChangedRegistrants.notifyResult(mPhysicalLinkState); |
| } |
| if (isAnyDataCallDormant && !isAnyDataCallActive) { |
| // There is no way to indicate link activity per APN right now. So |
| // Link Activity will be considered dormant only when all data calls |
| // are dormant. |
| // If a single data call is in dormant state and none of the data |
| // calls are active broadcast overall link state as dormant. |
| if (DBG) { |
| log("onDataStateChanged: Data activity DORMANT. stopNetStatePoll"); |
| } |
| mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT); |
| } else { |
| if (DBG) { |
| log("onDataStateChanged: Data Activity updated to NONE. " |
| + "isAnyDataCallActive = " + isAnyDataCallActive |
| + " isAnyDataCallDormant = " + isAnyDataCallDormant); |
| } |
| if (isAnyDataCallActive) { |
| mDct.sendStartNetStatPoll(DctConstants.Activity.NONE); |
| } |
| } |
| } |
| |
| if (DBG) { |
| log("onDataStateChanged: dcsToRetry=" + dcsToRetry |
| + " apnsToCleanup=" + apnsToCleanup); |
| } |
| |
| // Cleanup connections that have changed |
| for (ApnContext apnContext : apnsToCleanup) { |
| mDct.cleanUpConnection(apnContext); |
| } |
| |
| // Retry connections that have disappeared |
| for (DataConnection dc : dcsToRetry) { |
| if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag); |
| dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag); |
| } |
| |
| if (VDBG) log("onDataStateChanged: X"); |
| } |
| |
| /** |
| * Register for physical link state (i.e. RRC state) changed event. |
| * |
| * @param h The handler |
| * @param what The event |
| */ |
| void registerForPhysicalLinkStateChanged(Handler h, int what) { |
| mPhysicalLinkStateChangedRegistrants.addUnique(h, what, null); |
| } |
| |
| /** |
| * Unregister from physical link state (i.e. RRC state) changed event. |
| * |
| * @param h The previously registered handler |
| */ |
| void unregisterForPhysicalLinkStateChanged(Handler h) { |
| mPhysicalLinkStateChangedRegistrants.remove(h); |
| } |
| |
| private void log(String s) { |
| Rlog.d(mTag, s); |
| } |
| |
| private void loge(String s) { |
| Rlog.e(mTag, s); |
| } |
| |
| @Override |
| public String toString() { |
| synchronized (mDcListAll) { |
| return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid; |
| } |
| } |
| |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println(" mPhone=" + mPhone); |
| synchronized (mDcListAll) { |
| pw.println(" mDcListAll=" + mDcListAll); |
| pw.println(" mDcListActiveByCid=" + mDcListActiveByCid); |
| } |
| } |
| } |