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