• 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.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