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