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