DcController.java revision 454b1dfd508844b42eb775e4ab2359be74d3672b
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.Handler;
24import android.os.Message;
25import android.os.SystemProperties;
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    protected static final boolean DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1;
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 (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            for (DataCallResponse newState : dcsList) {
213                DataConnection dc = mDcListActiveByCid.get(newState.cid);
214                if (dc == null) {
215                    // UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed.
216                    loge("onDataStateChanged: no associated DC yet, ignore");
217                    continue;
218                }
219
220                if (dc.mApnContexts.size() == 0) {
221                    if (DBG) loge("onDataStateChanged: no connected apns, ignore");
222                } else {
223                    // Determine if the connection/apnContext should be cleaned up
224                    // or just a notification should be sent out.
225                    if (DBG) log("onDataStateChanged: Found ConnId=" + newState.cid
226                            + " newState=" + newState.toString());
227                    if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
228                        if (DBG) log("onDataStateChanged: inactive, add to retry list");
229                        dcsToRetry.add(dc);
230                    } else {
231                        // Its active so update the DataConnections link properties
232                        UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
233                        if (result.oldLp.equals(result.newLp)) {
234                            if (DBG) log("onDataStateChanged: no change");
235                        } else {
236                            if (result.oldLp.isIdenticalInterfaceName(result.newLp)) {
237                                if (! result.oldLp.isIdenticalDnses(result.newLp) ||
238                                        ! result.oldLp.isIdenticalRoutes(result.newLp) ||
239                                        ! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
240                                        ! result.oldLp.isIdenticalAddresses(result.newLp)) {
241                                    // If the same address type was removed and
242                                    // added we need to cleanup
243                                    CompareResult<LinkAddress> car =
244                                        result.oldLp.compareAddresses(result.newLp);
245                                    if (DBG) {
246                                        log("onDataStateChanged: oldLp=" + result.oldLp +
247                                                " newLp=" + result.newLp + " car=" + car);
248                                    }
249                                    boolean needToClean = false;
250                                    for (LinkAddress added : car.added) {
251                                        for (LinkAddress removed : car.removed) {
252                                            if (NetworkUtils.addressTypeMatches(
253                                                    removed.getAddress(),
254                                                    added.getAddress())) {
255                                                needToClean = true;
256                                                break;
257                                            }
258                                        }
259                                    }
260                                    if (needToClean) {
261                                        if (DBG) {
262                                            log("onDataStateChanged: addr change," +
263                                                    " cleanup apns=" + dc.mApnContexts +
264                                                    " oldLp=" + result.oldLp +
265                                                    " newLp=" + result.newLp);
266                                        }
267                                        apnsToCleanup.addAll(dc.mApnContexts);
268                                    } else {
269                                        if (DBG) log("onDataStateChanged: simple change");
270                                        for (ApnContext apnContext : dc.mApnContexts) {
271                                             mPhone.notifyDataConnection(
272                                                 PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
273                                                 apnContext.getApnType());
274                                        }
275                                    }
276                                } else {
277                                    if (DBG) {
278                                        log("onDataStateChanged: no changes");
279                                    }
280                                }
281                            } else {
282                                apnsToCleanup.addAll(dc.mApnContexts);
283                                if (DBG) {
284                                    log("onDataStateChanged: interface change, cleanup apns="
285                                            + dc.mApnContexts);
286                                }
287                            }
288                        }
289                    }
290                }
291            }
292            mPhone.notifyDataActivity();
293
294            if (DBG) {
295                lr("onDataStateChanged: dcsToRetry=" + dcsToRetry
296                        + " apnsToCleanup=" + apnsToCleanup);
297            }
298
299            // Cleanup connections that have changed
300            for (ApnContext apnContext : apnsToCleanup) {
301               mDct.sendCleanUpConnection(true, apnContext);
302            }
303
304            // Retry connections that have disappeared
305            for (DataConnection dc : dcsToRetry) {
306                if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag);
307                dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag);
308            }
309
310            if (DBG) log("onDataStateChanged: X");
311        }
312    }
313
314    /**
315     * lr is short name for logAndAddLogRec
316     * @param s
317     */
318    private void lr(String s) {
319        logAndAddLogRec(s);
320    }
321
322    @Override
323    protected void log(String s) {
324        Rlog.d(getName(), s);
325    }
326
327    @Override
328    protected void loge(String s) {
329        Rlog.e(getName(), s);
330    }
331
332    /**
333     * @return the string for msg.what as our info.
334     */
335    @Override
336    protected String getWhatToString(int what) {
337        String info = null;
338        info = DataConnection.cmdToString(what);
339        if (info == null) {
340            info = DcAsyncChannel.cmdToString(what);
341        }
342        return info;
343    }
344
345    @Override
346    public String toString() {
347        return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
348    }
349
350    @Override
351    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
352        super.dump(fd, pw, args);
353        pw.println(" mPhone=" + mPhone);
354        pw.println(" mDcListAll=" + mDcListAll);
355        pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
356    }
357}