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