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                        DcFailCause failCause = DcFailCause.fromInt(newState.status);
228                        if (DBG) log("onDataStateChanged: inactive failCause=" + failCause);
229                        if (failCause.isRestartRadioFail()) {
230                            if (DBG) log("onDataStateChanged: X restart radio");
231                            mDct.sendRestartRadio();
232                        } else if (failCause.isPermanentFail()) {
233                            if (DBG) log("onDataStateChanged: inactive, add to cleanup list");
234                            apnsToCleanup.addAll(dc.mApnContexts);
235                        } else {
236                            if (DBG) log("onDataStateChanged: inactive, add to retry list");
237                            dcsToRetry.add(dc);
238                        }
239                    } else {
240                        // Its active so update the DataConnections link properties
241                        UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
242                        if (result.oldLp.equals(result.newLp)) {
243                            if (DBG) log("onDataStateChanged: no change");
244                        } else {
245                            if (result.oldLp.isIdenticalInterfaceName(result.newLp)) {
246                                if (! result.oldLp.isIdenticalDnses(result.newLp) ||
247                                        ! result.oldLp.isIdenticalRoutes(result.newLp) ||
248                                        ! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
249                                        ! result.oldLp.isIdenticalAddresses(result.newLp)) {
250                                    // If the same address type was removed and
251                                    // added we need to cleanup
252                                    CompareResult<LinkAddress> car =
253                                        result.oldLp.compareAddresses(result.newLp);
254                                    if (DBG) {
255                                        log("onDataStateChanged: oldLp=" + result.oldLp +
256                                                " newLp=" + result.newLp + " car=" + car);
257                                    }
258                                    boolean needToClean = false;
259                                    for (LinkAddress added : car.added) {
260                                        for (LinkAddress removed : car.removed) {
261                                            if (NetworkUtils.addressTypeMatches(
262                                                    removed.getAddress(),
263                                                    added.getAddress())) {
264                                                needToClean = true;
265                                                break;
266                                            }
267                                        }
268                                    }
269                                    if (needToClean) {
270                                        if (DBG) {
271                                            log("onDataStateChanged: addr change," +
272                                                    " cleanup apns=" + dc.mApnContexts +
273                                                    " oldLp=" + result.oldLp +
274                                                    " newLp=" + result.newLp);
275                                        }
276                                        apnsToCleanup.addAll(dc.mApnContexts);
277                                    } else {
278                                        if (DBG) log("onDataStateChanged: simple change");
279                                        for (ApnContext apnContext : dc.mApnContexts) {
280                                             mPhone.notifyDataConnection(
281                                                 PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
282                                                 apnContext.getApnType());
283                                        }
284                                    }
285                                } else {
286                                    if (DBG) {
287                                        log("onDataStateChanged: no changes");
288                                    }
289                                }
290                            } else {
291                                apnsToCleanup.addAll(dc.mApnContexts);
292                                if (DBG) {
293                                    log("onDataStateChanged: interface change, cleanup apns="
294                                            + dc.mApnContexts);
295                                }
296                            }
297                        }
298                    }
299                }
300            }
301            mPhone.notifyDataActivity();
302
303            if (DBG) {
304                lr("onDataStateChanged: dcsToRetry=" + dcsToRetry
305                        + " apnsToCleanup=" + apnsToCleanup);
306            }
307
308            // Cleanup connections that have changed
309            for (ApnContext apnContext : apnsToCleanup) {
310               mDct.sendCleanUpConnection(true, apnContext);
311            }
312
313            // Retry connections that have disappeared
314            for (DataConnection dc : dcsToRetry) {
315                if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag);
316                dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag);
317            }
318
319            if (DBG) log("onDataStateChanged: X");
320        }
321    }
322
323    /**
324     * lr is short name for logAndAddLogRec
325     * @param s
326     */
327    private void lr(String s) {
328        logAndAddLogRec(s);
329    }
330
331    @Override
332    protected void log(String s) {
333        Rlog.d(getName(), s);
334    }
335
336    @Override
337    protected void loge(String s) {
338        Rlog.e(getName(), s);
339    }
340
341    /**
342     * @return the string for msg.what as our info.
343     */
344    @Override
345    protected String getWhatToString(int what) {
346        String info = null;
347        info = DataConnection.cmdToString(what);
348        if (info == null) {
349            info = DcAsyncChannel.cmdToString(what);
350        }
351        return info;
352    }
353
354    @Override
355    public String toString() {
356        return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
357    }
358
359    @Override
360    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
361        super.dump(fd, pw, args);
362        pw.println(" mPhone=" + mPhone);
363        pw.println(" mDcListAll=" + mDcListAll);
364        pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
365    }
366}