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