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