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