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 void removeActiveDcByCid(DataConnection dc) { 143 DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid); 144 if (DBG && removedDc == null) { 145 log("removeActiveDcByCid removedDc=null dc=" + dc); 146 } 147 } 148 149 boolean isExecutingCarrierChange() { 150 return mExecutingCarrierChange; 151 } 152 153 private class DccDefaultState extends State { 154 @Override 155 public void enter() { 156 mPhone.mCi.registerForRilConnected(getHandler(), 157 DataConnection.EVENT_RIL_CONNECTED, null); 158 mPhone.mCi.registerForDataNetworkStateChanged(getHandler(), 159 DataConnection.EVENT_DATA_STATE_CHANGED, null); 160 if (Build.IS_DEBUGGABLE) { 161 mDcTesterDeactivateAll = 162 new DcTesterDeactivateAll(mPhone, DcController.this, getHandler()); 163 } 164 } 165 166 @Override 167 public void exit() { 168 if (mPhone != null) { 169 mPhone.mCi.unregisterForRilConnected(getHandler()); 170 mPhone.mCi.unregisterForDataNetworkStateChanged(getHandler()); 171 } 172 if (mDcTesterDeactivateAll != null) { 173 mDcTesterDeactivateAll.dispose(); 174 } 175 } 176 177 @Override 178 public boolean processMessage(Message msg) { 179 AsyncResult ar; 180 181 switch (msg.what) { 182 case DataConnection.EVENT_RIL_CONNECTED: 183 ar = (AsyncResult)msg.obj; 184 if (ar.exception == null) { 185 if (DBG) { 186 log("DccDefaultState: msg.what=EVENT_RIL_CONNECTED mRilVersion=" + 187 ar.result); 188 } 189 } else { 190 log("DccDefaultState: Unexpected exception on EVENT_RIL_CONNECTED"); 191 } 192 break; 193 194 case DataConnection.EVENT_DATA_STATE_CHANGED: 195 ar = (AsyncResult)msg.obj; 196 if (ar.exception == null) { 197 onDataStateChanged((ArrayList<DataCallResponse>)ar.result); 198 } else { 199 log("DccDefaultState: EVENT_DATA_STATE_CHANGED:" + 200 " exception; likely radio not available, ignore"); 201 } 202 break; 203 } 204 return HANDLED; 205 } 206 207 /** 208 * Process the new list of "known" Data Calls 209 * @param dcsList as sent by RIL_UNSOL_DATA_CALL_LIST_CHANGED 210 */ 211 private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) { 212 if (DBG) { 213 lr("onDataStateChanged: dcsList=" + dcsList 214 + " mDcListActiveByCid=" + mDcListActiveByCid); 215 } 216 if (VDBG) { 217 log("onDataStateChanged: mDcListAll=" + mDcListAll); 218 } 219 220 // Create hashmap of cid to DataCallResponse 221 HashMap<Integer, DataCallResponse> dataCallResponseListByCid = 222 new HashMap<Integer, DataCallResponse>(); 223 for (DataCallResponse dcs : dcsList) { 224 dataCallResponseListByCid.put(dcs.cid, dcs); 225 } 226 227 // Add a DC that is active but not in the 228 // dcsList to the list of DC's to retry 229 ArrayList<DataConnection> dcsToRetry = new ArrayList<DataConnection>(); 230 for (DataConnection dc : mDcListActiveByCid.values()) { 231 if (dataCallResponseListByCid.get(dc.mCid) == null) { 232 if (DBG) log("onDataStateChanged: add to retry dc=" + dc); 233 dcsToRetry.add(dc); 234 } 235 } 236 if (DBG) log("onDataStateChanged: dcsToRetry=" + dcsToRetry); 237 238 // Find which connections have changed state and send a notification or cleanup 239 // and any that are in active need to be retried. 240 ArrayList<ApnContext> apnsToCleanup = new ArrayList<ApnContext>(); 241 242 boolean isAnyDataCallDormant = false; 243 boolean isAnyDataCallActive = false; 244 245 for (DataCallResponse newState : dcsList) { 246 247 DataConnection dc = mDcListActiveByCid.get(newState.cid); 248 if (dc == null) { 249 // UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed. 250 loge("onDataStateChanged: no associated DC yet, ignore"); 251 continue; 252 } 253 254 if (dc.mApnContexts.size() == 0) { 255 if (DBG) loge("onDataStateChanged: no connected apns, ignore"); 256 } else { 257 // Determine if the connection/apnContext should be cleaned up 258 // or just a notification should be sent out. 259 if (DBG) log("onDataStateChanged: Found ConnId=" + newState.cid 260 + " newState=" + newState.toString()); 261 if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) { 262 if (mDct.isCleanupRequired.get()) { 263 apnsToCleanup.addAll(dc.mApnContexts.keySet()); 264 mDct.isCleanupRequired.set(false); 265 } else { 266 DcFailCause failCause = DcFailCause.fromInt(newState.status); 267 if (failCause.isRestartRadioFail()) { 268 if (DBG) { 269 log("onDataStateChanged: X restart radio, failCause=" 270 + failCause); 271 } 272 mDct.sendRestartRadio(); 273 } else if (mDct.isPermanentFail(failCause)) { 274 if (DBG) { 275 log("onDataStateChanged: inactive, add to cleanup list. " 276 + "failCause=" + failCause); 277 } 278 apnsToCleanup.addAll(dc.mApnContexts.keySet()); 279 } else { 280 if (DBG) { 281 log("onDataStateChanged: inactive, add to retry list. " 282 + "failCause=" + failCause); 283 } 284 dcsToRetry.add(dc); 285 } 286 } 287 } else { 288 // Its active so update the DataConnections link properties 289 UpdateLinkPropertyResult result = dc.updateLinkProperty(newState); 290 if (result.oldLp.equals(result.newLp)) { 291 if (DBG) log("onDataStateChanged: no change"); 292 } else { 293 if (result.oldLp.isIdenticalInterfaceName(result.newLp)) { 294 if (! result.oldLp.isIdenticalDnses(result.newLp) || 295 ! result.oldLp.isIdenticalRoutes(result.newLp) || 296 ! result.oldLp.isIdenticalHttpProxy(result.newLp) || 297 ! result.oldLp.isIdenticalAddresses(result.newLp)) { 298 // If the same address type was removed and 299 // added we need to cleanup 300 CompareResult<LinkAddress> car = 301 result.oldLp.compareAddresses(result.newLp); 302 if (DBG) { 303 log("onDataStateChanged: oldLp=" + result.oldLp + 304 " newLp=" + result.newLp + " car=" + car); 305 } 306 boolean needToClean = false; 307 for (LinkAddress added : car.added) { 308 for (LinkAddress removed : car.removed) { 309 if (NetworkUtils.addressTypeMatches( 310 removed.getAddress(), 311 added.getAddress())) { 312 needToClean = true; 313 break; 314 } 315 } 316 } 317 if (needToClean) { 318 if (DBG) { 319 log("onDataStateChanged: addr change," + 320 " cleanup apns=" + dc.mApnContexts + 321 " oldLp=" + result.oldLp + 322 " newLp=" + result.newLp); 323 } 324 apnsToCleanup.addAll(dc.mApnContexts.keySet()); 325 } else { 326 if (DBG) log("onDataStateChanged: simple change"); 327 328 for (ApnContext apnContext : dc.mApnContexts.keySet()) { 329 mPhone.notifyDataConnection( 330 PhoneConstants.REASON_LINK_PROPERTIES_CHANGED, 331 apnContext.getApnType()); 332 } 333 } 334 } else { 335 if (DBG) { 336 log("onDataStateChanged: no changes"); 337 } 338 } 339 } else { 340 apnsToCleanup.addAll(dc.mApnContexts.keySet()); 341 if (DBG) { 342 log("onDataStateChanged: interface change, cleanup apns=" 343 + dc.mApnContexts); 344 } 345 } 346 } 347 } 348 } 349 350 if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_UP) { 351 isAnyDataCallActive = true; 352 } 353 if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT) { 354 isAnyDataCallDormant = true; 355 } 356 } 357 358 if (isAnyDataCallDormant && !isAnyDataCallActive) { 359 // There is no way to indicate link activity per APN right now. So 360 // Link Activity will be considered dormant only when all data calls 361 // are dormant. 362 // If a single data call is in dormant state and none of the data 363 // calls are active broadcast overall link state as dormant. 364 if (DBG) { 365 log("onDataStateChanged: Data Activity updated to DORMANT. stopNetStatePoll"); 366 } 367 mDct.sendStopNetStatPoll(DctConstants.Activity.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 mDct.sendStartNetStatPoll(DctConstants.Activity.NONE); 376 } 377 } 378 379 if (DBG) { 380 lr("onDataStateChanged: dcsToRetry=" + dcsToRetry 381 + " apnsToCleanup=" + apnsToCleanup); 382 } 383 384 // Cleanup connections that have changed 385 for (ApnContext apnContext : apnsToCleanup) { 386 mDct.sendCleanUpConnection(true, apnContext); 387 } 388 389 // Retry connections that have disappeared 390 for (DataConnection dc : dcsToRetry) { 391 if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag); 392 dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag); 393 } 394 395 if (VDBG) log("onDataStateChanged: X"); 396 } 397 } 398 399 /** 400 * lr is short name for logAndAddLogRec 401 * @param s 402 */ 403 private void lr(String s) { 404 logAndAddLogRec(s); 405 } 406 407 @Override 408 protected void log(String s) { 409 Rlog.d(getName(), s); 410 } 411 412 @Override 413 protected void loge(String s) { 414 Rlog.e(getName(), s); 415 } 416 417 /** 418 * @return the string for msg.what as our info. 419 */ 420 @Override 421 protected String getWhatToString(int what) { 422 String info = null; 423 info = DataConnection.cmdToString(what); 424 if (info == null) { 425 info = DcAsyncChannel.cmdToString(what); 426 } 427 return info; 428 } 429 430 @Override 431 public String toString() { 432 return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid; 433 } 434 435 @Override 436 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 437 super.dump(fd, pw, args); 438 pw.println(" mPhone=" + mPhone); 439 pw.println(" mDcListAll=" + mDcListAll); 440 pw.println(" mDcListActiveByCid=" + mDcListActiveByCid); 441 } 442} 443