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.NetworkUtils; 22import android.net.LinkProperties.CompareResult; 23import android.os.AsyncResult; 24import android.os.Build; 25import android.os.Handler; 26import android.os.Message; 27import android.telephony.TelephonyManager; 28import android.telephony.PhoneStateListener; 29import android.telephony.Rlog; 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.registerForDataNetworkStateChanged(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.unregisterForDataNetworkStateChanged(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()) { 272 if (DBG) { 273 log("onDataStateChanged: X restart radio, failCause=" 274 + failCause); 275 } 276 mDct.sendRestartRadio(); 277 } else if (mDct.isPermanentFail(failCause)) { 278 if (DBG) { 279 log("onDataStateChanged: inactive, add to cleanup list. " 280 + "failCause=" + failCause); 281 } 282 apnsToCleanup.addAll(dc.mApnContexts.keySet()); 283 } else { 284 if (DBG) { 285 log("onDataStateChanged: inactive, add to retry list. " 286 + "failCause=" + failCause); 287 } 288 dcsToRetry.add(dc); 289 } 290 } 291 } else { 292 // Its active so update the DataConnections link properties 293 UpdateLinkPropertyResult result = dc.updateLinkProperty(newState); 294 if (result.oldLp.equals(result.newLp)) { 295 if (DBG) log("onDataStateChanged: no change"); 296 } else { 297 if (result.oldLp.isIdenticalInterfaceName(result.newLp)) { 298 if (! result.oldLp.isIdenticalDnses(result.newLp) || 299 ! result.oldLp.isIdenticalRoutes(result.newLp) || 300 ! result.oldLp.isIdenticalHttpProxy(result.newLp) || 301 ! result.oldLp.isIdenticalAddresses(result.newLp)) { 302 // If the same address type was removed and 303 // added we need to cleanup 304 CompareResult<LinkAddress> car = 305 result.oldLp.compareAddresses(result.newLp); 306 if (DBG) { 307 log("onDataStateChanged: oldLp=" + result.oldLp + 308 " newLp=" + result.newLp + " car=" + car); 309 } 310 boolean needToClean = false; 311 for (LinkAddress added : car.added) { 312 for (LinkAddress removed : car.removed) { 313 if (NetworkUtils.addressTypeMatches( 314 removed.getAddress(), 315 added.getAddress())) { 316 needToClean = true; 317 break; 318 } 319 } 320 } 321 if (needToClean) { 322 if (DBG) { 323 log("onDataStateChanged: addr change," + 324 " cleanup apns=" + dc.mApnContexts + 325 " oldLp=" + result.oldLp + 326 " newLp=" + result.newLp); 327 } 328 apnsToCleanup.addAll(dc.mApnContexts.keySet()); 329 } else { 330 if (DBG) log("onDataStateChanged: simple change"); 331 332 for (ApnContext apnContext : dc.mApnContexts.keySet()) { 333 mPhone.notifyDataConnection( 334 PhoneConstants.REASON_LINK_PROPERTIES_CHANGED, 335 apnContext.getApnType()); 336 } 337 } 338 } else { 339 if (DBG) { 340 log("onDataStateChanged: no changes"); 341 } 342 } 343 } else { 344 apnsToCleanup.addAll(dc.mApnContexts.keySet()); 345 if (DBG) { 346 log("onDataStateChanged: interface change, cleanup apns=" 347 + dc.mApnContexts); 348 } 349 } 350 } 351 } 352 } 353 354 if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_UP) { 355 isAnyDataCallActive = true; 356 } 357 if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT) { 358 isAnyDataCallDormant = true; 359 } 360 } 361 362 if (isAnyDataCallDormant && !isAnyDataCallActive) { 363 // There is no way to indicate link activity per APN right now. So 364 // Link Activity will be considered dormant only when all data calls 365 // are dormant. 366 // If a single data call is in dormant state and none of the data 367 // calls are active broadcast overall link state as dormant. 368 if (DBG) { 369 log("onDataStateChanged: Data Activity updated to DORMANT. stopNetStatePoll"); 370 } 371 mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT); 372 } else { 373 if (DBG) { 374 log("onDataStateChanged: Data Activity updated to NONE. " + 375 "isAnyDataCallActive = " + isAnyDataCallActive + 376 " isAnyDataCallDormant = " + isAnyDataCallDormant); 377 } 378 if (isAnyDataCallActive) { 379 mDct.sendStartNetStatPoll(DctConstants.Activity.NONE); 380 } 381 } 382 383 if (DBG) { 384 lr("onDataStateChanged: dcsToRetry=" + dcsToRetry 385 + " apnsToCleanup=" + apnsToCleanup); 386 } 387 388 // Cleanup connections that have changed 389 for (ApnContext apnContext : apnsToCleanup) { 390 mDct.sendCleanUpConnection(true, apnContext); 391 } 392 393 // Retry connections that have disappeared 394 for (DataConnection dc : dcsToRetry) { 395 if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag); 396 dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag); 397 } 398 399 if (VDBG) log("onDataStateChanged: X"); 400 } 401 } 402 403 /** 404 * lr is short name for logAndAddLogRec 405 * @param s 406 */ 407 private void lr(String s) { 408 logAndAddLogRec(s); 409 } 410 411 @Override 412 protected void log(String s) { 413 Rlog.d(getName(), s); 414 } 415 416 @Override 417 protected void loge(String s) { 418 Rlog.e(getName(), s); 419 } 420 421 /** 422 * @return the string for msg.what as our info. 423 */ 424 @Override 425 protected String getWhatToString(int what) { 426 String info = null; 427 info = DataConnection.cmdToString(what); 428 if (info == null) { 429 info = DcAsyncChannel.cmdToString(what); 430 } 431 return info; 432 } 433 434 @Override 435 public String toString() { 436 return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid; 437 } 438 439 @Override 440 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 441 super.dump(fd, pw, args); 442 pw.println(" mPhone=" + mPhone); 443 pw.println(" mDcListAll=" + mDcListAll); 444 pw.println(" mDcListActiveByCid=" + mDcListActiveByCid); 445 } 446} 447