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    public DataConnection getActiveDcByCid(int cid) {
143        return mDcListActiveByCid.get(cid);
144    }
145
146    void removeActiveDcByCid(DataConnection dc) {
147        DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid);
148        if (DBG && removedDc == null) {
149            log("removeActiveDcByCid removedDc=null dc=" + dc);
150        }
151    }
152
153    boolean isExecutingCarrierChange() {
154        return mExecutingCarrierChange;
155    }
156
157    private class DccDefaultState extends State {
158        @Override
159        public void enter() {
160            mPhone.mCi.registerForRilConnected(getHandler(),
161                    DataConnection.EVENT_RIL_CONNECTED, null);
162            mPhone.mCi.registerForDataNetworkStateChanged(getHandler(),
163                    DataConnection.EVENT_DATA_STATE_CHANGED, null);
164            if (Build.IS_DEBUGGABLE) {
165                mDcTesterDeactivateAll =
166                        new DcTesterDeactivateAll(mPhone, DcController.this, getHandler());
167            }
168        }
169
170        @Override
171        public void exit() {
172            if (mPhone != null) {
173                mPhone.mCi.unregisterForRilConnected(getHandler());
174                mPhone.mCi.unregisterForDataNetworkStateChanged(getHandler());
175            }
176            if (mDcTesterDeactivateAll != null) {
177                mDcTesterDeactivateAll.dispose();
178            }
179        }
180
181        @Override
182        public boolean processMessage(Message msg) {
183            AsyncResult ar;
184
185            switch (msg.what) {
186                case DataConnection.EVENT_RIL_CONNECTED:
187                    ar = (AsyncResult)msg.obj;
188                    if (ar.exception == null) {
189                        if (DBG) {
190                            log("DccDefaultState: msg.what=EVENT_RIL_CONNECTED mRilVersion=" +
191                                ar.result);
192                        }
193                    } else {
194                        log("DccDefaultState: Unexpected exception on EVENT_RIL_CONNECTED");
195                    }
196                    break;
197
198                case DataConnection.EVENT_DATA_STATE_CHANGED:
199                    ar = (AsyncResult)msg.obj;
200                    if (ar.exception == null) {
201                        onDataStateChanged((ArrayList<DataCallResponse>)ar.result);
202                    } else {
203                        log("DccDefaultState: EVENT_DATA_STATE_CHANGED:" +
204                                    " exception; likely radio not available, ignore");
205                    }
206                    break;
207            }
208            return HANDLED;
209        }
210
211        /**
212         * Process the new list of "known" Data Calls
213         * @param dcsList as sent by RIL_UNSOL_DATA_CALL_LIST_CHANGED
214         */
215        private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) {
216            if (DBG) {
217                lr("onDataStateChanged: dcsList=" + dcsList
218                        + " mDcListActiveByCid=" + mDcListActiveByCid);
219            }
220            if (VDBG) {
221                log("onDataStateChanged: mDcListAll=" + mDcListAll);
222            }
223
224            // Create hashmap of cid to DataCallResponse
225            HashMap<Integer, DataCallResponse> dataCallResponseListByCid =
226                    new HashMap<Integer, DataCallResponse>();
227            for (DataCallResponse dcs : dcsList) {
228                dataCallResponseListByCid.put(dcs.cid, dcs);
229            }
230
231            // Add a DC that is active but not in the
232            // dcsList to the list of DC's to retry
233            ArrayList<DataConnection> dcsToRetry = new ArrayList<DataConnection>();
234            for (DataConnection dc : mDcListActiveByCid.values()) {
235                if (dataCallResponseListByCid.get(dc.mCid) == null) {
236                    if (DBG) log("onDataStateChanged: add to retry dc=" + dc);
237                    dcsToRetry.add(dc);
238                }
239            }
240            if (DBG) log("onDataStateChanged: dcsToRetry=" + dcsToRetry);
241
242            // Find which connections have changed state and send a notification or cleanup
243            // and any that are in active need to be retried.
244            ArrayList<ApnContext> apnsToCleanup = new ArrayList<ApnContext>();
245
246            boolean isAnyDataCallDormant = false;
247            boolean isAnyDataCallActive = false;
248
249            for (DataCallResponse newState : dcsList) {
250
251                DataConnection dc = mDcListActiveByCid.get(newState.cid);
252                if (dc == null) {
253                    // UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed.
254                    loge("onDataStateChanged: no associated DC yet, ignore");
255                    continue;
256                }
257
258                if (dc.mApnContexts.size() == 0) {
259                    if (DBG) loge("onDataStateChanged: no connected apns, ignore");
260                } else {
261                    // Determine if the connection/apnContext should be cleaned up
262                    // or just a notification should be sent out.
263                    if (DBG) log("onDataStateChanged: Found ConnId=" + newState.cid
264                            + " newState=" + newState.toString());
265                    if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
266                        if (mDct.isCleanupRequired.get()) {
267                            apnsToCleanup.addAll(dc.mApnContexts.keySet());
268                            mDct.isCleanupRequired.set(false);
269                        } else {
270                            DcFailCause failCause = DcFailCause.fromInt(newState.status);
271                            if (failCause.isRestartRadioFail()) {
272                                if (DBG) {
273                                    log("onDataStateChanged: X restart radio, failCause="
274                                            + failCause);
275                                }
276                                mDct.sendRestartRadio();
277                            } else if (mDct.isPermanentFail(failCause)) {
278                                if (DBG) {
279                                    log("onDataStateChanged: inactive, add to cleanup list. "
280                                            + "failCause=" + failCause);
281                                }
282                                apnsToCleanup.addAll(dc.mApnContexts.keySet());
283                            } else {
284                                if (DBG) {
285                                    log("onDataStateChanged: inactive, add to retry list. "
286                                            + "failCause=" + failCause);
287                                }
288                                dcsToRetry.add(dc);
289                            }
290                        }
291                    } else {
292                        // Its active so update the DataConnections link properties
293                        UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
294                        if (result.oldLp.equals(result.newLp)) {
295                            if (DBG) log("onDataStateChanged: no change");
296                        } else {
297                            if (result.oldLp.isIdenticalInterfaceName(result.newLp)) {
298                                if (! result.oldLp.isIdenticalDnses(result.newLp) ||
299                                        ! result.oldLp.isIdenticalRoutes(result.newLp) ||
300                                        ! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
301                                        ! result.oldLp.isIdenticalAddresses(result.newLp)) {
302                                    // If the same address type was removed and
303                                    // added we need to cleanup
304                                    CompareResult<LinkAddress> car =
305                                        result.oldLp.compareAddresses(result.newLp);
306                                    if (DBG) {
307                                        log("onDataStateChanged: oldLp=" + result.oldLp +
308                                                " newLp=" + result.newLp + " car=" + car);
309                                    }
310                                    boolean needToClean = false;
311                                    for (LinkAddress added : car.added) {
312                                        for (LinkAddress removed : car.removed) {
313                                            if (NetworkUtils.addressTypeMatches(
314                                                    removed.getAddress(),
315                                                    added.getAddress())) {
316                                                needToClean = true;
317                                                break;
318                                            }
319                                        }
320                                    }
321                                    if (needToClean) {
322                                        if (DBG) {
323                                            log("onDataStateChanged: addr change," +
324                                                    " cleanup apns=" + dc.mApnContexts +
325                                                    " oldLp=" + result.oldLp +
326                                                    " newLp=" + result.newLp);
327                                        }
328                                        apnsToCleanup.addAll(dc.mApnContexts.keySet());
329                                    } else {
330                                        if (DBG) log("onDataStateChanged: simple change");
331
332                                        for (ApnContext apnContext : dc.mApnContexts.keySet()) {
333                                             mPhone.notifyDataConnection(
334                                                 PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
335                                                 apnContext.getApnType());
336                                        }
337                                    }
338                                } else {
339                                    if (DBG) {
340                                        log("onDataStateChanged: no changes");
341                                    }
342                                }
343                            } else {
344                                apnsToCleanup.addAll(dc.mApnContexts.keySet());
345                                if (DBG) {
346                                    log("onDataStateChanged: interface change, cleanup apns="
347                                            + dc.mApnContexts);
348                                }
349                            }
350                        }
351                    }
352                }
353
354                if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_UP) {
355                    isAnyDataCallActive = true;
356                }
357                if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT) {
358                    isAnyDataCallDormant = true;
359                }
360            }
361
362            if (isAnyDataCallDormant && !isAnyDataCallActive) {
363                // There is no way to indicate link activity per APN right now. So
364                // Link Activity will be considered dormant only when all data calls
365                // are dormant.
366                // If a single data call is in dormant state and none of the data
367                // calls are active broadcast overall link state as dormant.
368                if (DBG) {
369                    log("onDataStateChanged: Data Activity updated to DORMANT. stopNetStatePoll");
370                }
371                mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT);
372            } else {
373                if (DBG) {
374                    log("onDataStateChanged: Data Activity updated to NONE. " +
375                            "isAnyDataCallActive = " + isAnyDataCallActive +
376                            " isAnyDataCallDormant = " + isAnyDataCallDormant);
377                }
378                if (isAnyDataCallActive) {
379                    mDct.sendStartNetStatPoll(DctConstants.Activity.NONE);
380                }
381            }
382
383            if (DBG) {
384                lr("onDataStateChanged: dcsToRetry=" + dcsToRetry
385                        + " apnsToCleanup=" + apnsToCleanup);
386            }
387
388            // Cleanup connections that have changed
389            for (ApnContext apnContext : apnsToCleanup) {
390               mDct.sendCleanUpConnection(true, apnContext);
391            }
392
393            // Retry connections that have disappeared
394            for (DataConnection dc : dcsToRetry) {
395                if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag);
396                dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag);
397            }
398
399            if (VDBG) log("onDataStateChanged: X");
400        }
401    }
402
403    /**
404     * lr is short name for logAndAddLogRec
405     * @param s
406     */
407    private void lr(String s) {
408        logAndAddLogRec(s);
409    }
410
411    @Override
412    protected void log(String s) {
413        Rlog.d(getName(), s);
414    }
415
416    @Override
417    protected void loge(String s) {
418        Rlog.e(getName(), s);
419    }
420
421    /**
422     * @return the string for msg.what as our info.
423     */
424    @Override
425    protected String getWhatToString(int what) {
426        String info = null;
427        info = DataConnection.cmdToString(what);
428        if (info == null) {
429            info = DcAsyncChannel.cmdToString(what);
430        }
431        return info;
432    }
433
434    @Override
435    public String toString() {
436        return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
437    }
438
439    @Override
440    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
441        super.dump(fd, pw, args);
442        pw.println(" mPhone=" + mPhone);
443        pw.println(" mDcListAll=" + mDcListAll);
444        pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
445    }
446}
447