Home | History | Annotate | Download | only in dataconnection
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.telephony.dataconnection;
     18 
     19 import android.content.Context;
     20 import android.net.INetworkPolicyListener;
     21 import android.net.LinkAddress;
     22 import android.net.LinkProperties.CompareResult;
     23 import android.net.NetworkPolicyManager;
     24 import android.net.NetworkUtils;
     25 import android.os.AsyncResult;
     26 import android.os.Build;
     27 import android.os.Handler;
     28 import android.os.Message;
     29 import android.telephony.AccessNetworkConstants.TransportType;
     30 import android.telephony.PhoneStateListener;
     31 import android.telephony.Rlog;
     32 import android.telephony.TelephonyManager;
     33 import android.telephony.data.DataCallResponse;
     34 
     35 import com.android.internal.telephony.DctConstants;
     36 import com.android.internal.telephony.Phone;
     37 import com.android.internal.telephony.PhoneConstants;
     38 import com.android.internal.telephony.dataconnection.DataConnection.UpdateLinkPropertyResult;
     39 import com.android.internal.util.State;
     40 import com.android.internal.util.StateMachine;
     41 
     42 import java.io.FileDescriptor;
     43 import java.io.PrintWriter;
     44 import java.util.ArrayList;
     45 import java.util.HashMap;
     46 
     47 /**
     48  * Data Connection Controller which is a package visible class and controls
     49  * multiple data connections. For instance listening for unsolicited messages
     50  * and then demultiplexing them to the appropriate DC.
     51  */
     52 public class DcController extends StateMachine {
     53     private static final boolean DBG = true;
     54     private static final boolean VDBG = false;
     55 
     56     private final Phone mPhone;
     57     private final DcTracker mDct;
     58     private final DataServiceManager mDataServiceManager;
     59     private final DcTesterDeactivateAll mDcTesterDeactivateAll;
     60 
     61     // package as its used by Testing code
     62     // @GuardedBy("mDcListAll")
     63     final ArrayList<DataConnection> mDcListAll = new ArrayList<>();
     64     // @GuardedBy("mDcListAll")
     65     private final HashMap<Integer, DataConnection> mDcListActiveByCid = new HashMap<>();
     66 
     67     /**
     68      * Constants for the data connection activity:
     69      * physical link down/up
     70      *
     71      * TODO: Move to RILConstants.java
     72      */
     73     static final int DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE = 0;
     74     static final int DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT = 1;
     75     static final int DATA_CONNECTION_ACTIVE_PH_LINK_UP = 2;
     76     static final int DATA_CONNECTION_ACTIVE_UNKNOWN = Integer.MAX_VALUE;
     77 
     78     private DccDefaultState mDccDefaultState = new DccDefaultState();
     79 
     80     final TelephonyManager mTelephonyManager;
     81     final NetworkPolicyManager mNetworkPolicyManager;
     82 
     83     private PhoneStateListener mPhoneStateListener;
     84 
     85     //mExecutingCarrierChange tracks whether the phone is currently executing
     86     //carrier network change
     87     private volatile boolean mExecutingCarrierChange;
     88 
     89     /**
     90      * Constructor.
     91      *
     92      * @param name to be used for the Controller
     93      * @param phone the phone associated with Dcc and Dct
     94      * @param dct the DataConnectionTracker associated with Dcc
     95      * @param dataServiceManager the data service manager that manages data services
     96      * @param handler defines the thread/looper to be used with Dcc
     97      */
     98     private DcController(String name, Phone phone, DcTracker dct,
     99                          DataServiceManager dataServiceManager, Handler handler) {
    100         super(name, handler);
    101         setLogRecSize(300);
    102         log("E ctor");
    103         mPhone = phone;
    104         mDct = dct;
    105         mDataServiceManager = dataServiceManager;
    106         addState(mDccDefaultState);
    107         setInitialState(mDccDefaultState);
    108         log("X ctor");
    109 
    110         mPhoneStateListener = new PhoneStateListener(handler.getLooper()) {
    111             @Override
    112             public void onCarrierNetworkChange(boolean active) {
    113                 mExecutingCarrierChange = active;
    114             }
    115         };
    116 
    117         mTelephonyManager = (TelephonyManager) phone.getContext()
    118                 .getSystemService(Context.TELEPHONY_SERVICE);
    119         mNetworkPolicyManager = (NetworkPolicyManager) phone.getContext()
    120                 .getSystemService(Context.NETWORK_POLICY_SERVICE);
    121 
    122         mDcTesterDeactivateAll = (Build.IS_DEBUGGABLE)
    123                 ? new DcTesterDeactivateAll(mPhone, DcController.this, getHandler())
    124                 : null;
    125 
    126         if (mTelephonyManager != null) {
    127             mTelephonyManager.listen(mPhoneStateListener,
    128                     PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE);
    129         }
    130     }
    131 
    132     public static DcController makeDcc(Phone phone, DcTracker dct,
    133                                        DataServiceManager dataServiceManager, Handler handler) {
    134         DcController dcc = new DcController("Dcc", phone, dct, dataServiceManager, handler);
    135         return dcc;
    136     }
    137 
    138     void dispose() {
    139         log("dispose: call quiteNow()");
    140         if(mTelephonyManager != null) mTelephonyManager.listen(mPhoneStateListener, 0);
    141         quitNow();
    142     }
    143 
    144     void addDc(DataConnection dc) {
    145         synchronized (mDcListAll) {
    146             mDcListAll.add(dc);
    147         }
    148     }
    149 
    150     void removeDc(DataConnection dc) {
    151         synchronized (mDcListAll) {
    152             mDcListActiveByCid.remove(dc.mCid);
    153             mDcListAll.remove(dc);
    154         }
    155     }
    156 
    157     public void addActiveDcByCid(DataConnection dc) {
    158         if (DBG && dc.mCid < 0) {
    159             log("addActiveDcByCid dc.mCid < 0 dc=" + dc);
    160         }
    161         synchronized (mDcListAll) {
    162             mDcListActiveByCid.put(dc.mCid, dc);
    163         }
    164     }
    165 
    166     public DataConnection getActiveDcByCid(int cid) {
    167         synchronized (mDcListAll) {
    168             return mDcListActiveByCid.get(cid);
    169         }
    170     }
    171 
    172     void removeActiveDcByCid(DataConnection dc) {
    173         synchronized (mDcListAll) {
    174             DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid);
    175             if (DBG && removedDc == null) {
    176                 log("removeActiveDcByCid removedDc=null dc=" + dc);
    177             }
    178         }
    179     }
    180 
    181     boolean isExecutingCarrierChange() {
    182         return mExecutingCarrierChange;
    183     }
    184 
    185     private final INetworkPolicyListener mListener = new NetworkPolicyManager.Listener() {
    186         @Override
    187         public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) {
    188             if (mPhone == null || mPhone.getSubId() != subId) return;
    189 
    190             final HashMap<Integer, DataConnection> dcListActiveByCid;
    191             synchronized (mDcListAll) {
    192                 dcListActiveByCid = new HashMap<>(mDcListActiveByCid);
    193             }
    194             for (DataConnection dc : dcListActiveByCid.values()) {
    195                 dc.onSubscriptionOverride(overrideMask, overrideValue);
    196             }
    197         }
    198     };
    199 
    200     private class DccDefaultState extends State {
    201         @Override
    202         public void enter() {
    203             if (mPhone != null && mDataServiceManager.getTransportType()
    204                     == TransportType.WWAN) {
    205                 mPhone.mCi.registerForRilConnected(getHandler(),
    206                         DataConnection.EVENT_RIL_CONNECTED, null);
    207             }
    208 
    209             mDataServiceManager.registerForDataCallListChanged(getHandler(),
    210                     DataConnection.EVENT_DATA_STATE_CHANGED);
    211 
    212             if (mNetworkPolicyManager != null) {
    213                 mNetworkPolicyManager.registerListener(mListener);
    214             }
    215         }
    216 
    217         @Override
    218         public void exit() {
    219             if (mPhone != null & mDataServiceManager.getTransportType()
    220                     == TransportType.WWAN) {
    221                 mPhone.mCi.unregisterForRilConnected(getHandler());
    222             }
    223             mDataServiceManager.unregisterForDataCallListChanged(getHandler());
    224 
    225             if (mDcTesterDeactivateAll != null) {
    226                 mDcTesterDeactivateAll.dispose();
    227             }
    228             if (mNetworkPolicyManager != null) {
    229                 mNetworkPolicyManager.unregisterListener(mListener);
    230             }
    231         }
    232 
    233         @Override
    234         public boolean processMessage(Message msg) {
    235             AsyncResult ar;
    236 
    237             switch (msg.what) {
    238                 case DataConnection.EVENT_RIL_CONNECTED:
    239                     ar = (AsyncResult)msg.obj;
    240                     if (ar.exception == null) {
    241                         if (DBG) {
    242                             log("DccDefaultState: msg.what=EVENT_RIL_CONNECTED mRilVersion=" +
    243                                 ar.result);
    244                         }
    245                     } else {
    246                         log("DccDefaultState: Unexpected exception on EVENT_RIL_CONNECTED");
    247                     }
    248                     break;
    249 
    250                 case DataConnection.EVENT_DATA_STATE_CHANGED:
    251                     ar = (AsyncResult)msg.obj;
    252                     if (ar.exception == null) {
    253                         onDataStateChanged((ArrayList<DataCallResponse>)ar.result);
    254                     } else {
    255                         log("DccDefaultState: EVENT_DATA_STATE_CHANGED:" +
    256                                     " exception; likely radio not available, ignore");
    257                     }
    258                     break;
    259             }
    260             return HANDLED;
    261         }
    262 
    263         /**
    264          * Process the new list of "known" Data Calls
    265          * @param dcsList as sent by RIL_UNSOL_DATA_CALL_LIST_CHANGED
    266          */
    267         private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) {
    268             final ArrayList<DataConnection> dcListAll;
    269             final HashMap<Integer, DataConnection> dcListActiveByCid;
    270             synchronized (mDcListAll) {
    271                 dcListAll = new ArrayList<>(mDcListAll);
    272                 dcListActiveByCid = new HashMap<>(mDcListActiveByCid);
    273             }
    274 
    275             if (DBG) {
    276                 lr("onDataStateChanged: dcsList=" + dcsList
    277                         + " dcListActiveByCid=" + dcListActiveByCid);
    278             }
    279             if (VDBG) {
    280                 log("onDataStateChanged: mDcListAll=" + dcListAll);
    281             }
    282 
    283             // Create hashmap of cid to DataCallResponse
    284             HashMap<Integer, DataCallResponse> dataCallResponseListByCid =
    285                     new HashMap<Integer, DataCallResponse>();
    286             for (DataCallResponse dcs : dcsList) {
    287                 dataCallResponseListByCid.put(dcs.getCallId(), dcs);
    288             }
    289 
    290             // Add a DC that is active but not in the
    291             // dcsList to the list of DC's to retry
    292             ArrayList<DataConnection> dcsToRetry = new ArrayList<DataConnection>();
    293             for (DataConnection dc : dcListActiveByCid.values()) {
    294                 if (dataCallResponseListByCid.get(dc.mCid) == null) {
    295                     if (DBG) log("onDataStateChanged: add to retry dc=" + dc);
    296                     dcsToRetry.add(dc);
    297                 }
    298             }
    299             if (DBG) log("onDataStateChanged: dcsToRetry=" + dcsToRetry);
    300 
    301             // Find which connections have changed state and send a notification or cleanup
    302             // and any that are in active need to be retried.
    303             ArrayList<ApnContext> apnsToCleanup = new ArrayList<ApnContext>();
    304 
    305             boolean isAnyDataCallDormant = false;
    306             boolean isAnyDataCallActive = false;
    307 
    308             for (DataCallResponse newState : dcsList) {
    309 
    310                 DataConnection dc = dcListActiveByCid.get(newState.getCallId());
    311                 if (dc == null) {
    312                     // UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed.
    313                     loge("onDataStateChanged: no associated DC yet, ignore");
    314                     continue;
    315                 }
    316 
    317                 if (dc.mApnContexts.size() == 0) {
    318                     if (DBG) loge("onDataStateChanged: no connected apns, ignore");
    319                 } else {
    320                     // Determine if the connection/apnContext should be cleaned up
    321                     // or just a notification should be sent out.
    322                     if (DBG) {
    323                         log("onDataStateChanged: Found ConnId=" + newState.getCallId()
    324                                 + " newState=" + newState.toString());
    325                     }
    326                     if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
    327                         if (mDct.isCleanupRequired.get()) {
    328                             apnsToCleanup.addAll(dc.mApnContexts.keySet());
    329                             mDct.isCleanupRequired.set(false);
    330                         } else {
    331                             DcFailCause failCause = DcFailCause.fromInt(newState.getStatus());
    332                             if (failCause.isRestartRadioFail(mPhone.getContext(),
    333                                         mPhone.getSubId())) {
    334                                 if (DBG) {
    335                                     log("onDataStateChanged: X restart radio, failCause="
    336                                             + failCause);
    337                                 }
    338                                 mDct.sendRestartRadio();
    339                             } else if (mDct.isPermanentFailure(failCause)) {
    340                                 if (DBG) {
    341                                     log("onDataStateChanged: inactive, add to cleanup list. "
    342                                             + "failCause=" + failCause);
    343                                 }
    344                                 apnsToCleanup.addAll(dc.mApnContexts.keySet());
    345                             } else {
    346                                 if (DBG) {
    347                                     log("onDataStateChanged: inactive, add to retry list. "
    348                                             + "failCause=" + failCause);
    349                                 }
    350                                 dcsToRetry.add(dc);
    351                             }
    352                         }
    353                     } else {
    354                         // Its active so update the DataConnections link properties
    355                         UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
    356                         if (result.oldLp.equals(result.newLp)) {
    357                             if (DBG) log("onDataStateChanged: no change");
    358                         } else {
    359                             if (result.oldLp.isIdenticalInterfaceName(result.newLp)) {
    360                                 if (! result.oldLp.isIdenticalDnses(result.newLp) ||
    361                                         ! result.oldLp.isIdenticalRoutes(result.newLp) ||
    362                                         ! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
    363                                         ! result.oldLp.isIdenticalAddresses(result.newLp)) {
    364                                     // If the same address type was removed and
    365                                     // added we need to cleanup
    366                                     CompareResult<LinkAddress> car =
    367                                         result.oldLp.compareAddresses(result.newLp);
    368                                     if (DBG) {
    369                                         log("onDataStateChanged: oldLp=" + result.oldLp +
    370                                                 " newLp=" + result.newLp + " car=" + car);
    371                                     }
    372                                     boolean needToClean = false;
    373                                     for (LinkAddress added : car.added) {
    374                                         for (LinkAddress removed : car.removed) {
    375                                             if (NetworkUtils.addressTypeMatches(
    376                                                     removed.getAddress(),
    377                                                     added.getAddress())) {
    378                                                 needToClean = true;
    379                                                 break;
    380                                             }
    381                                         }
    382                                     }
    383                                     if (needToClean) {
    384                                         if (DBG) {
    385                                             log("onDataStateChanged: addr change," +
    386                                                     " cleanup apns=" + dc.mApnContexts +
    387                                                     " oldLp=" + result.oldLp +
    388                                                     " newLp=" + result.newLp);
    389                                         }
    390                                         apnsToCleanup.addAll(dc.mApnContexts.keySet());
    391                                     } else {
    392                                         if (DBG) log("onDataStateChanged: simple change");
    393 
    394                                         for (ApnContext apnContext : dc.mApnContexts.keySet()) {
    395                                              mPhone.notifyDataConnection(
    396                                                  PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
    397                                                  apnContext.getApnType());
    398                                         }
    399                                     }
    400                                 } else {
    401                                     if (DBG) {
    402                                         log("onDataStateChanged: no changes");
    403                                     }
    404                                 }
    405                             } else {
    406                                 apnsToCleanup.addAll(dc.mApnContexts.keySet());
    407                                 if (DBG) {
    408                                     log("onDataStateChanged: interface change, cleanup apns="
    409                                             + dc.mApnContexts);
    410                                 }
    411                             }
    412                         }
    413                     }
    414                 }
    415 
    416                 if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_UP) {
    417                     isAnyDataCallActive = true;
    418                 }
    419                 if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT) {
    420                     isAnyDataCallDormant = true;
    421                 }
    422             }
    423 
    424             if (isAnyDataCallDormant && !isAnyDataCallActive) {
    425                 // There is no way to indicate link activity per APN right now. So
    426                 // Link Activity will be considered dormant only when all data calls
    427                 // are dormant.
    428                 // If a single data call is in dormant state and none of the data
    429                 // calls are active broadcast overall link state as dormant.
    430                 if (DBG) {
    431                     log("onDataStateChanged: Data Activity updated to DORMANT. stopNetStatePoll");
    432                 }
    433                 mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT);
    434             } else {
    435                 if (DBG) {
    436                     log("onDataStateChanged: Data Activity updated to NONE. " +
    437                             "isAnyDataCallActive = " + isAnyDataCallActive +
    438                             " isAnyDataCallDormant = " + isAnyDataCallDormant);
    439                 }
    440                 if (isAnyDataCallActive) {
    441                     mDct.sendStartNetStatPoll(DctConstants.Activity.NONE);
    442                 }
    443             }
    444 
    445             if (DBG) {
    446                 lr("onDataStateChanged: dcsToRetry=" + dcsToRetry
    447                         + " apnsToCleanup=" + apnsToCleanup);
    448             }
    449 
    450             // Cleanup connections that have changed
    451             for (ApnContext apnContext : apnsToCleanup) {
    452                mDct.sendCleanUpConnection(true, apnContext);
    453             }
    454 
    455             // Retry connections that have disappeared
    456             for (DataConnection dc : dcsToRetry) {
    457                 if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag);
    458                 dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag);
    459             }
    460 
    461             if (VDBG) log("onDataStateChanged: X");
    462         }
    463     }
    464 
    465     /**
    466      * lr is short name for logAndAddLogRec
    467      * @param s
    468      */
    469     private void lr(String s) {
    470         logAndAddLogRec(s);
    471     }
    472 
    473     @Override
    474     protected void log(String s) {
    475         Rlog.d(getName(), s);
    476     }
    477 
    478     @Override
    479     protected void loge(String s) {
    480         Rlog.e(getName(), s);
    481     }
    482 
    483     /**
    484      * @return the string for msg.what as our info.
    485      */
    486     @Override
    487     protected String getWhatToString(int what) {
    488         String info = null;
    489         info = DataConnection.cmdToString(what);
    490         if (info == null) {
    491             info = DcAsyncChannel.cmdToString(what);
    492         }
    493         return info;
    494     }
    495 
    496     @Override
    497     public String toString() {
    498         synchronized (mDcListAll) {
    499             return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
    500         }
    501     }
    502 
    503     @Override
    504     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    505         super.dump(fd, pw, args);
    506         pw.println(" mPhone=" + mPhone);
    507         synchronized (mDcListAll) {
    508             pw.println(" mDcListAll=" + mDcListAll);
    509             pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
    510         }
    511     }
    512 }
    513