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