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