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