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.net.LinkAddress;
     20 import android.net.NetworkUtils;
     21 import android.net.LinkProperties.CompareResult;
     22 import android.os.AsyncResult;
     23 import android.os.Build;
     24 import android.os.Handler;
     25 import android.os.Message;
     26 import android.telephony.Rlog;
     27 import com.android.internal.telephony.PhoneBase;
     28 import com.android.internal.telephony.PhoneConstants;
     29 import com.android.internal.telephony.dataconnection.DataConnection.UpdateLinkPropertyResult;
     30 import com.android.internal.util.State;
     31 import com.android.internal.util.StateMachine;
     32 
     33 import java.io.FileDescriptor;
     34 import java.io.PrintWriter;
     35 import java.util.ArrayList;
     36 import java.util.HashMap;
     37 
     38 /**
     39  * Data Connection Controller which is a package visible class and controls
     40  * multiple data connections. For instance listening for unsolicited messages
     41  * and then demultiplexing them to the appropriate DC.
     42  */
     43 class DcController extends StateMachine {
     44     private static final boolean DBG = true;
     45     private static final boolean VDBG = false;
     46 
     47     private PhoneBase mPhone;
     48     private DcTrackerBase mDct;
     49     private DcTesterDeactivateAll mDcTesterDeactivateAll;
     50 
     51     // package as its used by Testing code
     52     ArrayList<DataConnection> mDcListAll = new ArrayList<DataConnection>();
     53     private HashMap<Integer, DataConnection> mDcListActiveByCid =
     54             new HashMap<Integer, DataConnection>();
     55 
     56     /**
     57      * Constants for the data connection activity:
     58      * physical link down/up
     59      *
     60      * TODO: Move to RILConstants.java
     61      */
     62     static final int DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE = 0;
     63     static final int DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT = 1;
     64     static final int DATA_CONNECTION_ACTIVE_PH_LINK_UP = 2;
     65 
     66     private DccDefaultState mDccDefaultState = new DccDefaultState();
     67 
     68     /**
     69      * Constructor.
     70      *
     71      * @param name to be used for the Controller
     72      * @param phone the phone associated with Dcc and Dct
     73      * @param dct the DataConnectionTracker associated with Dcc
     74      * @param handler defines the thread/looper to be used with Dcc
     75      */
     76     private DcController(String name, PhoneBase phone, DcTrackerBase dct,
     77             Handler handler) {
     78         super(name, handler);
     79         setLogRecSize(300);
     80         log("E ctor");
     81         mPhone = phone;
     82         mDct = dct;
     83         addState(mDccDefaultState);
     84         setInitialState(mDccDefaultState);
     85         log("X ctor");
     86     }
     87 
     88     static DcController makeDcc(PhoneBase phone, DcTrackerBase dct, Handler handler) {
     89         DcController dcc = new DcController("Dcc", phone, dct, handler);
     90         dcc.start();
     91         return dcc;
     92     }
     93 
     94     void dispose() {
     95         log("dispose: call quiteNow()");
     96         quitNow();
     97     }
     98 
     99     void addDc(DataConnection dc) {
    100         mDcListAll.add(dc);
    101     }
    102 
    103     void removeDc(DataConnection dc) {
    104         mDcListActiveByCid.remove(dc.mCid);
    105         mDcListAll.remove(dc);
    106     }
    107 
    108     void addActiveDcByCid(DataConnection dc) {
    109         if (DBG && dc.mCid < 0) {
    110             log("addActiveDcByCid dc.mCid < 0 dc=" + dc);
    111         }
    112         mDcListActiveByCid.put(dc.mCid, dc);
    113     }
    114 
    115     void removeActiveDcByCid(DataConnection dc) {
    116         DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid);
    117         if (DBG && removedDc == null) {
    118             log("removeActiveDcByCid removedDc=null dc=" + dc);
    119         }
    120     }
    121 
    122     private class DccDefaultState extends State {
    123         @Override
    124         public void enter() {
    125             mPhone.mCi.registerForRilConnected(getHandler(),
    126                     DataConnection.EVENT_RIL_CONNECTED, null);
    127             mPhone.mCi.registerForDataNetworkStateChanged(getHandler(),
    128                     DataConnection.EVENT_DATA_STATE_CHANGED, null);
    129             if (Build.IS_DEBUGGABLE) {
    130                 mDcTesterDeactivateAll =
    131                         new DcTesterDeactivateAll(mPhone, DcController.this, getHandler());
    132             }
    133         }
    134 
    135         @Override
    136         public void exit() {
    137             if (mPhone != null) {
    138                 mPhone.mCi.unregisterForRilConnected(getHandler());
    139                 mPhone.mCi.unregisterForDataNetworkStateChanged(getHandler());
    140             }
    141             if (mDcTesterDeactivateAll != null) {
    142                 mDcTesterDeactivateAll.dispose();
    143             }
    144         }
    145 
    146         @Override
    147         public boolean processMessage(Message msg) {
    148             AsyncResult ar;
    149 
    150             switch (msg.what) {
    151                 case DataConnection.EVENT_RIL_CONNECTED:
    152                     ar = (AsyncResult)msg.obj;
    153                     if (ar.exception == null) {
    154                         if (DBG) {
    155                             log("DccDefaultState: msg.what=EVENT_RIL_CONNECTED mRilVersion=" +
    156                                 ar.result);
    157                         }
    158                     } else {
    159                         log("DccDefaultState: Unexpected exception on EVENT_RIL_CONNECTED");
    160                     }
    161                     break;
    162 
    163                 case DataConnection.EVENT_DATA_STATE_CHANGED:
    164                     ar = (AsyncResult)msg.obj;
    165                     if (ar.exception == null) {
    166                         onDataStateChanged((ArrayList<DataCallResponse>)ar.result);
    167                     } else {
    168                         log("DccDefaultState: EVENT_DATA_STATE_CHANGED:" +
    169                                     " exception; likely radio not available, ignore");
    170                     }
    171                     break;
    172             }
    173             return HANDLED;
    174         }
    175 
    176         /**
    177          * Process the new list of "known" Data Calls
    178          * @param dcsList as sent by RIL_UNSOL_DATA_CALL_LIST_CHANGED
    179          */
    180         private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) {
    181             if (DBG) {
    182                 lr("onDataStateChanged: dcsList=" + dcsList
    183                         + " mDcListActiveByCid=" + mDcListActiveByCid);
    184             }
    185             if (VDBG) {
    186                 log("onDataStateChanged: mDcListAll=" + mDcListAll);
    187             }
    188 
    189             // Create hashmap of cid to DataCallResponse
    190             HashMap<Integer, DataCallResponse> dataCallResponseListByCid =
    191                     new HashMap<Integer, DataCallResponse>();
    192             for (DataCallResponse dcs : dcsList) {
    193                 dataCallResponseListByCid.put(dcs.cid, dcs);
    194             }
    195 
    196             // Add a DC that is active but not in the
    197             // dcsList to the list of DC's to retry
    198             ArrayList<DataConnection> dcsToRetry = new ArrayList<DataConnection>();
    199             for (DataConnection dc : mDcListActiveByCid.values()) {
    200                 if (dataCallResponseListByCid.get(dc.mCid) == null) {
    201                     if (DBG) log("onDataStateChanged: add to retry dc=" + dc);
    202                     dcsToRetry.add(dc);
    203                 }
    204             }
    205             if (DBG) log("onDataStateChanged: dcsToRetry=" + dcsToRetry);
    206 
    207             // Find which connections have changed state and send a notification or cleanup
    208             // and any that are in active need to be retried.
    209             ArrayList<ApnContext> apnsToCleanup = new ArrayList<ApnContext>();
    210 
    211             for (DataCallResponse newState : dcsList) {
    212                 DataConnection dc = mDcListActiveByCid.get(newState.cid);
    213                 if (dc == null) {
    214                     // UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed.
    215                     loge("onDataStateChanged: no associated DC yet, ignore");
    216                     continue;
    217                 }
    218 
    219                 if (dc.mApnContexts.size() == 0) {
    220                     if (DBG) loge("onDataStateChanged: no connected apns, ignore");
    221                 } else {
    222                     // Determine if the connection/apnContext should be cleaned up
    223                     // or just a notification should be sent out.
    224                     if (DBG) log("onDataStateChanged: Found ConnId=" + newState.cid
    225                             + " newState=" + newState.toString());
    226                     if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
    227                         DcFailCause failCause = DcFailCause.fromInt(newState.status);
    228                         if (DBG) log("onDataStateChanged: inactive failCause=" + failCause);
    229                         if (failCause.isRestartRadioFail()) {
    230                             if (DBG) log("onDataStateChanged: X restart radio");
    231                             mDct.sendRestartRadio();
    232                         } else if (failCause.isPermanentFail()) {
    233                             if (DBG) log("onDataStateChanged: inactive, add to cleanup list");
    234                             apnsToCleanup.addAll(dc.mApnContexts);
    235                         } else {
    236                             if (DBG) log("onDataStateChanged: inactive, add to retry list");
    237                             dcsToRetry.add(dc);
    238                         }
    239                     } else {
    240                         // Its active so update the DataConnections link properties
    241                         UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
    242                         if (result.oldLp.equals(result.newLp)) {
    243                             if (DBG) log("onDataStateChanged: no change");
    244                         } else {
    245                             if (result.oldLp.isIdenticalInterfaceName(result.newLp)) {
    246                                 if (! result.oldLp.isIdenticalDnses(result.newLp) ||
    247                                         ! result.oldLp.isIdenticalRoutes(result.newLp) ||
    248                                         ! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
    249                                         ! result.oldLp.isIdenticalAddresses(result.newLp)) {
    250                                     // If the same address type was removed and
    251                                     // added we need to cleanup
    252                                     CompareResult<LinkAddress> car =
    253                                         result.oldLp.compareAddresses(result.newLp);
    254                                     if (DBG) {
    255                                         log("onDataStateChanged: oldLp=" + result.oldLp +
    256                                                 " newLp=" + result.newLp + " car=" + car);
    257                                     }
    258                                     boolean needToClean = false;
    259                                     for (LinkAddress added : car.added) {
    260                                         for (LinkAddress removed : car.removed) {
    261                                             if (NetworkUtils.addressTypeMatches(
    262                                                     removed.getAddress(),
    263                                                     added.getAddress())) {
    264                                                 needToClean = true;
    265                                                 break;
    266                                             }
    267                                         }
    268                                     }
    269                                     if (needToClean) {
    270                                         if (DBG) {
    271                                             log("onDataStateChanged: addr change," +
    272                                                     " cleanup apns=" + dc.mApnContexts +
    273                                                     " oldLp=" + result.oldLp +
    274                                                     " newLp=" + result.newLp);
    275                                         }
    276                                         apnsToCleanup.addAll(dc.mApnContexts);
    277                                     } else {
    278                                         if (DBG) log("onDataStateChanged: simple change");
    279                                         for (ApnContext apnContext : dc.mApnContexts) {
    280                                              mPhone.notifyDataConnection(
    281                                                  PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
    282                                                  apnContext.getApnType());
    283                                         }
    284                                     }
    285                                 } else {
    286                                     if (DBG) {
    287                                         log("onDataStateChanged: no changes");
    288                                     }
    289                                 }
    290                             } else {
    291                                 apnsToCleanup.addAll(dc.mApnContexts);
    292                                 if (DBG) {
    293                                     log("onDataStateChanged: interface change, cleanup apns="
    294                                             + dc.mApnContexts);
    295                                 }
    296                             }
    297                         }
    298                     }
    299                 }
    300             }
    301             mPhone.notifyDataActivity();
    302 
    303             if (DBG) {
    304                 lr("onDataStateChanged: dcsToRetry=" + dcsToRetry
    305                         + " apnsToCleanup=" + apnsToCleanup);
    306             }
    307 
    308             // Cleanup connections that have changed
    309             for (ApnContext apnContext : apnsToCleanup) {
    310                mDct.sendCleanUpConnection(true, apnContext);
    311             }
    312 
    313             // Retry connections that have disappeared
    314             for (DataConnection dc : dcsToRetry) {
    315                 if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag);
    316                 dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag);
    317             }
    318 
    319             if (DBG) log("onDataStateChanged: X");
    320         }
    321     }
    322 
    323     /**
    324      * lr is short name for logAndAddLogRec
    325      * @param s
    326      */
    327     private void lr(String s) {
    328         logAndAddLogRec(s);
    329     }
    330 
    331     @Override
    332     protected void log(String s) {
    333         Rlog.d(getName(), s);
    334     }
    335 
    336     @Override
    337     protected void loge(String s) {
    338         Rlog.e(getName(), s);
    339     }
    340 
    341     /**
    342      * @return the string for msg.what as our info.
    343      */
    344     @Override
    345     protected String getWhatToString(int what) {
    346         String info = null;
    347         info = DataConnection.cmdToString(what);
    348         if (info == null) {
    349             info = DcAsyncChannel.cmdToString(what);
    350         }
    351         return info;
    352     }
    353 
    354     @Override
    355     public String toString() {
    356         return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
    357     }
    358 
    359     @Override
    360     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    361         super.dump(fd, pw, args);
    362         pw.println(" mPhone=" + mPhone);
    363         pw.println(" mDcListAll=" + mDcListAll);
    364         pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
    365     }
    366 }