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