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.annotation.IntDef; 20 import android.hardware.radio.V1_4.DataConnActiveStatus; 21 import android.net.LinkAddress; 22 import android.os.AsyncResult; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.os.RegistrantList; 27 import android.telephony.AccessNetworkConstants; 28 import android.telephony.DataFailCause; 29 import android.telephony.data.ApnSetting; 30 import android.telephony.data.DataCallResponse; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.telephony.DctConstants; 34 import com.android.internal.telephony.Phone; 35 import com.android.internal.telephony.dataconnection.DataConnection.UpdateLinkPropertyResult; 36 import com.android.internal.telephony.util.TelephonyUtils; 37 import com.android.net.module.util.LinkPropertiesUtils; 38 import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult; 39 import com.android.net.module.util.NetUtils; 40 import com.android.telephony.Rlog; 41 42 import java.io.FileDescriptor; 43 import java.io.PrintWriter; 44 import java.lang.annotation.Retention; 45 import java.lang.annotation.RetentionPolicy; 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Objects; 50 51 /** 52 * Data Connection Controller which is a package visible class and controls 53 * multiple data connections. For instance listening for unsolicited messages 54 * and then demultiplexing them to the appropriate DC. 55 */ 56 public class DcController extends Handler { 57 private static final boolean DBG = true; 58 private static final boolean VDBG = false; 59 60 /** Physical link state unknown */ 61 public static final int PHYSICAL_LINK_UNKNOWN = 0; 62 63 /** Physical link state inactive (i.e. RRC idle) */ 64 public static final int PHYSICAL_LINK_NOT_ACTIVE = 1; 65 66 /** Physical link state active (i.e. RRC connected) */ 67 public static final int PHYSICAL_LINK_ACTIVE = 2; 68 69 /** @hide */ 70 @IntDef(prefix = { "PHYSICAL_LINK_" }, value = { 71 PHYSICAL_LINK_UNKNOWN, 72 PHYSICAL_LINK_NOT_ACTIVE, 73 PHYSICAL_LINK_ACTIVE 74 }) 75 @Retention(RetentionPolicy.SOURCE) 76 public @interface PhysicalLinkState{} 77 78 private final Phone mPhone; 79 private final DcTracker mDct; 80 private final String mTag; 81 private final DataServiceManager mDataServiceManager; 82 private final DcTesterDeactivateAll mDcTesterDeactivateAll; 83 84 // package as its used by Testing code 85 // @GuardedBy("mDcListAll") 86 final ArrayList<DataConnection> mDcListAll = new ArrayList<>(); 87 // @GuardedBy("mDcListAll") 88 private final HashMap<Integer, DataConnection> mDcListActiveByCid = new HashMap<>(); 89 90 /** 91 * Aggregated physical link state from all data connections. This reflects the device's RRC 92 * connection state. 93 * If {@link CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL} is true, 94 * then This reflects "internet data connection" instead of RRC state. 95 */ 96 private @PhysicalLinkState int mPhysicalLinkState = PHYSICAL_LINK_UNKNOWN; 97 98 private RegistrantList mPhysicalLinkStateChangedRegistrants = new RegistrantList(); 99 100 /** 101 * Constructor. 102 * 103 * @param name to be used for the Controller 104 * @param phone the phone associated with Dcc and Dct 105 * @param dct the DataConnectionTracker associated with Dcc 106 * @param dataServiceManager the data service manager that manages data services 107 * @param looper looper for this handler 108 */ DcController(String name, Phone phone, DcTracker dct, DataServiceManager dataServiceManager, Looper looper)109 private DcController(String name, Phone phone, DcTracker dct, 110 DataServiceManager dataServiceManager, Looper looper) { 111 super(looper); 112 mPhone = phone; 113 mDct = dct; 114 mTag = name; 115 mDataServiceManager = dataServiceManager; 116 117 mDcTesterDeactivateAll = (TelephonyUtils.IS_DEBUGGABLE) 118 ? new DcTesterDeactivateAll(mPhone, DcController.this, this) 119 : null; 120 mDataServiceManager.registerForDataCallListChanged(this, 121 DataConnection.EVENT_DATA_STATE_CHANGED); 122 } 123 makeDcc(Phone phone, DcTracker dct, DataServiceManager dataServiceManager, Looper looper, String tagSuffix)124 public static DcController makeDcc(Phone phone, DcTracker dct, 125 DataServiceManager dataServiceManager, Looper looper, 126 String tagSuffix) { 127 return new DcController("Dcc" + tagSuffix, phone, dct, dataServiceManager, looper); 128 } 129 addDc(DataConnection dc)130 void addDc(DataConnection dc) { 131 synchronized (mDcListAll) { 132 mDcListAll.add(dc); 133 } 134 } 135 removeDc(DataConnection dc)136 void removeDc(DataConnection dc) { 137 synchronized (mDcListAll) { 138 mDcListActiveByCid.remove(dc.mCid); 139 mDcListAll.remove(dc); 140 } 141 } 142 addActiveDcByCid(DataConnection dc)143 public void addActiveDcByCid(DataConnection dc) { 144 if (DBG && dc.mCid < 0) { 145 log("addActiveDcByCid dc.mCid < 0 dc=" + dc); 146 } 147 synchronized (mDcListAll) { 148 mDcListActiveByCid.put(dc.mCid, dc); 149 } 150 } 151 getActiveDcByCid(int cid)152 DataConnection getActiveDcByCid(int cid) { 153 synchronized (mDcListAll) { 154 return mDcListActiveByCid.get(cid); 155 } 156 } 157 removeActiveDcByCid(DataConnection dc)158 void removeActiveDcByCid(DataConnection dc) { 159 synchronized (mDcListAll) { 160 DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid); 161 if (DBG && removedDc == null) { 162 log("removeActiveDcByCid removedDc=null dc=" + dc); 163 } 164 } 165 } 166 isDefaultDataActive()167 boolean isDefaultDataActive() { 168 synchronized (mDcListAll) { 169 return mDcListActiveByCid.values().stream() 170 .anyMatch(dc -> dc.getApnContexts().stream() 171 .anyMatch(apn -> apn.getApnTypeBitmask() == ApnSetting.TYPE_DEFAULT)); 172 } 173 } 174 175 @Override handleMessage(Message msg)176 public void handleMessage(Message msg) { 177 AsyncResult ar; 178 179 switch (msg.what) { 180 case DataConnection.EVENT_DATA_STATE_CHANGED: 181 ar = (AsyncResult) msg.obj; 182 if (ar.exception == null) { 183 onDataStateChanged((ArrayList<DataCallResponse>) ar.result); 184 } else { 185 log("EVENT_DATA_STATE_CHANGED: exception; likely radio not available, ignore"); 186 } 187 break; 188 default: 189 loge("Unexpected event " + msg); 190 break; 191 } 192 } 193 194 /** 195 * Process the new list of "known" Data Calls 196 * @param dcsList as sent by RIL_UNSOL_DATA_CALL_LIST_CHANGED 197 */ onDataStateChanged(ArrayList<DataCallResponse> dcsList)198 private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) { 199 final HashMap<Integer, DataConnection> dcListActiveByCid; 200 synchronized (mDcListAll) { 201 dcListActiveByCid = new HashMap<>(mDcListActiveByCid); 202 } 203 204 if (DBG) { 205 log("onDataStateChanged: dcsList=" + dcsList 206 + " dcListActiveByCid=" + dcListActiveByCid); 207 } 208 209 // Create hashmap of cid to DataCallResponse 210 HashMap<Integer, DataCallResponse> dataCallResponseListByCid = 211 new HashMap<Integer, DataCallResponse>(); 212 for (DataCallResponse dcs : dcsList) { 213 dataCallResponseListByCid.put(dcs.getId(), dcs); 214 } 215 216 // Add a DC that is active but not in the 217 // dcsList to the list of DC's to retry 218 ArrayList<DataConnection> dcsToRetry = new ArrayList<DataConnection>(); 219 for (DataConnection dc : dcListActiveByCid.values()) { 220 if (dataCallResponseListByCid.get(dc.mCid) == null) { 221 if (DBG) log("onDataStateChanged: add to retry dc=" + dc); 222 dcsToRetry.add(dc); 223 } 224 } 225 if (DBG) log("onDataStateChanged: dcsToRetry=" + dcsToRetry); 226 227 // Find which connections have changed state and send a notification or cleanup 228 // and any that are in active need to be retried. 229 ArrayList<ApnContext> apnsToCleanup = new ArrayList<ApnContext>(); 230 231 boolean isAnyDataCallDormant = false; 232 boolean isAnyDataCallActive = false; 233 boolean isInternetDataCallActive = false; 234 235 for (DataCallResponse newState : dcsList) { 236 237 DataConnection dc = dcListActiveByCid.get(newState.getId()); 238 if (dc == null) { 239 // UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed. 240 loge("onDataStateChanged: no associated DC yet, ignore"); 241 continue; 242 } 243 244 List<ApnContext> apnContexts = dc.getApnContexts(); 245 if (apnContexts.size() == 0) { 246 if (DBG) loge("onDataStateChanged: no connected apns, ignore"); 247 } else { 248 // Determine if the connection/apnContext should be cleaned up 249 // or just a notification should be sent out. 250 if (DBG) { 251 log("onDataStateChanged: Found ConnId=" + newState.getId() 252 + " newState=" + newState.toString()); 253 } 254 if (apnContexts.stream().anyMatch( 255 i -> ApnSetting.TYPE_DEFAULT_STRING.equals(i.getApnType())) 256 && newState.getLinkStatus() == DataConnActiveStatus.ACTIVE) { 257 isInternetDataCallActive = true; 258 } 259 if (newState.getLinkStatus() == DataConnActiveStatus.INACTIVE) { 260 if (mDct.isCleanupRequired.get()) { 261 apnsToCleanup.addAll(apnContexts); 262 mDct.isCleanupRequired.set(false); 263 } else { 264 int failCause = DataFailCause.getFailCause(newState.getCause()); 265 if (DataFailCause.isRadioRestartFailure(mPhone.getContext(), failCause, 266 mPhone.getSubId())) { 267 if (DBG) { 268 log("onDataStateChanged: X restart radio, failCause=" 269 + failCause); 270 } 271 mDct.sendRestartRadio(); 272 } else if (mDct.isPermanentFailure(failCause)) { 273 if (DBG) { 274 log("onDataStateChanged: inactive, add to cleanup list. " 275 + "failCause=" + failCause); 276 } 277 apnsToCleanup.addAll(apnContexts); 278 } else { 279 if (DBG) { 280 log("onDataStateChanged: inactive, add to retry list. " 281 + "failCause=" + failCause); 282 } 283 dcsToRetry.add(dc); 284 } 285 } 286 } else { 287 // Update the pdu session id 288 dc.setPduSessionId(newState.getPduSessionId()); 289 290 dc.updatePcscfAddr(newState); 291 292 // Its active so update the DataConnections link properties 293 UpdateLinkPropertyResult result = dc.updateLinkProperty(newState); 294 dc.updateResponseFields(newState); 295 if (result.oldLp.equals(result.newLp)) { 296 if (DBG) log("onDataStateChanged: no change"); 297 } else { 298 if (LinkPropertiesUtils.isIdenticalInterfaceName( 299 result.oldLp, result.newLp)) { 300 if (!LinkPropertiesUtils.isIdenticalDnses( 301 result.oldLp, result.newLp) 302 || !LinkPropertiesUtils.isIdenticalRoutes( 303 result.oldLp, result.newLp) 304 || !LinkPropertiesUtils.isIdenticalHttpProxy( 305 result.oldLp, result.newLp) 306 || !LinkPropertiesUtils.isIdenticalAddresses( 307 result.oldLp, result.newLp)) { 308 // If the same address type was removed and 309 // added we need to cleanup 310 CompareOrUpdateResult<Integer, LinkAddress> car = 311 new CompareOrUpdateResult( 312 result.oldLp != null 313 ? result.oldLp.getLinkAddresses() : null, 314 result.newLp != null 315 ? result.newLp.getLinkAddresses() : null, 316 (la) -> Objects.hash(((LinkAddress) la) 317 .getAddress(), 318 ((LinkAddress) la).getPrefixLength(), 319 ((LinkAddress) la).getScope())); 320 if (DBG) { 321 log("onDataStateChanged: oldLp=" + result.oldLp 322 + " newLp=" + result.newLp + " car=" + car); 323 } 324 boolean needToClean = false; 325 for (LinkAddress added : car.added) { 326 for (LinkAddress removed : car.removed) { 327 if (NetUtils.addressTypeMatches( 328 removed.getAddress(), 329 added.getAddress())) { 330 needToClean = true; 331 break; 332 } 333 } 334 } 335 if (needToClean) { 336 if (DBG) { 337 log("onDataStateChanged: addr change," 338 + " cleanup apns=" + apnContexts 339 + " oldLp=" + result.oldLp 340 + " newLp=" + result.newLp); 341 } 342 apnsToCleanup.addAll(apnContexts); 343 } 344 } else { 345 if (DBG) { 346 log("onDataStateChanged: no changes"); 347 } 348 } 349 } else { 350 apnsToCleanup.addAll(apnContexts); 351 if (DBG) { 352 log("onDataStateChanged: interface change, cleanup apns=" 353 + apnContexts); 354 } 355 } 356 } 357 } 358 } 359 360 if (newState.getLinkStatus() == DataConnActiveStatus.ACTIVE) { 361 isAnyDataCallActive = true; 362 } 363 if (newState.getLinkStatus() == DataConnActiveStatus.DORMANT) { 364 isAnyDataCallDormant = true; 365 } 366 } 367 368 if (mDataServiceManager.getTransportType() 369 == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { 370 boolean isPhysicalLinkStateFocusingOnInternetData = 371 mDct.getLteEndcUsingUserDataForIdleDetection(); 372 int physicalLinkState = 373 (isPhysicalLinkStateFocusingOnInternetData 374 ? isInternetDataCallActive : isAnyDataCallActive) 375 ? PHYSICAL_LINK_ACTIVE : PHYSICAL_LINK_NOT_ACTIVE; 376 if (mPhysicalLinkState != physicalLinkState) { 377 mPhysicalLinkState = physicalLinkState; 378 mPhysicalLinkStateChangedRegistrants.notifyResult(mPhysicalLinkState); 379 } 380 if (isAnyDataCallDormant && !isAnyDataCallActive) { 381 // There is no way to indicate link activity per APN right now. So 382 // Link Activity will be considered dormant only when all data calls 383 // are dormant. 384 // If a single data call is in dormant state and none of the data 385 // calls are active broadcast overall link state as dormant. 386 if (DBG) { 387 log("onDataStateChanged: Data activity DORMANT. stopNetStatePoll"); 388 } 389 mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT); 390 } else { 391 if (DBG) { 392 log("onDataStateChanged: Data Activity updated to NONE. " 393 + "isAnyDataCallActive = " + isAnyDataCallActive 394 + " isAnyDataCallDormant = " + isAnyDataCallDormant); 395 } 396 if (isAnyDataCallActive) { 397 mDct.sendStartNetStatPoll(DctConstants.Activity.NONE); 398 } 399 } 400 } 401 402 if (DBG) { 403 log("onDataStateChanged: dcsToRetry=" + dcsToRetry 404 + " apnsToCleanup=" + apnsToCleanup); 405 } 406 407 // Cleanup connections that have changed 408 for (ApnContext apnContext : apnsToCleanup) { 409 mDct.cleanUpConnection(apnContext); 410 } 411 412 // Retry connections that have disappeared 413 for (DataConnection dc : dcsToRetry) { 414 if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag); 415 dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag); 416 } 417 418 if (VDBG) log("onDataStateChanged: X"); 419 } 420 421 /** 422 * Register for physical link state (i.e. RRC state) changed event. 423 * if {@link CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL} is true, 424 * then physical link state is focusing on "internet data connection" instead of RRC state. 425 * @param h The handler 426 * @param what The event 427 */ 428 @VisibleForTesting registerForPhysicalLinkStateChanged(Handler h, int what)429 public void registerForPhysicalLinkStateChanged(Handler h, int what) { 430 mPhysicalLinkStateChangedRegistrants.addUnique(h, what, null); 431 } 432 433 /** 434 * Unregister from physical link state (i.e. RRC state) changed event. 435 * 436 * @param h The previously registered handler 437 */ unregisterForPhysicalLinkStateChanged(Handler h)438 void unregisterForPhysicalLinkStateChanged(Handler h) { 439 mPhysicalLinkStateChangedRegistrants.remove(h); 440 } 441 log(String s)442 private void log(String s) { 443 Rlog.d(mTag, s); 444 } 445 loge(String s)446 private void loge(String s) { 447 Rlog.e(mTag, s); 448 } 449 450 @Override toString()451 public String toString() { 452 synchronized (mDcListAll) { 453 return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid; 454 } 455 } 456 dump(FileDescriptor fd, PrintWriter pw, String[] args)457 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 458 pw.println(" mPhone=" + mPhone); 459 synchronized (mDcListAll) { 460 pw.println(" mDcListAll=" + mDcListAll); 461 pw.println(" mDcListActiveByCid=" + mDcListActiveByCid); 462 } 463 } 464 } 465