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