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