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