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