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.LinkProperties.CompareResult;
22import android.net.NetworkUtils;
23import android.os.AsyncResult;
24import android.os.Build;
25import android.os.Handler;
26import android.os.Message;
27import android.telephony.PhoneStateListener;
28import android.telephony.Rlog;
29import android.telephony.TelephonyManager;
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.registerForDataCallListChanged(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.unregisterForDataCallListChanged(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(mPhone.getContext(),
272                                        mPhone.getSubId())) {
273                                if (DBG) {
274                                    log("onDataStateChanged: X restart radio, failCause="
275                                            + failCause);
276                                }
277                                mDct.sendRestartRadio();
278                            } else if (mDct.isPermanentFailure(failCause)) {
279                                if (DBG) {
280                                    log("onDataStateChanged: inactive, add to cleanup list. "
281                                            + "failCause=" + failCause);
282                                }
283                                apnsToCleanup.addAll(dc.mApnContexts.keySet());
284                            } else {
285                                if (DBG) {
286                                    log("onDataStateChanged: inactive, add to retry list. "
287                                            + "failCause=" + failCause);
288                                }
289                                dcsToRetry.add(dc);
290                            }
291                        }
292                    } else {
293                        // Its active so update the DataConnections link properties
294                        UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
295                        if (result.oldLp.equals(result.newLp)) {
296                            if (DBG) log("onDataStateChanged: no change");
297                        } else {
298                            if (result.oldLp.isIdenticalInterfaceName(result.newLp)) {
299                                if (! result.oldLp.isIdenticalDnses(result.newLp) ||
300                                        ! result.oldLp.isIdenticalRoutes(result.newLp) ||
301                                        ! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
302                                        ! result.oldLp.isIdenticalAddresses(result.newLp)) {
303                                    // If the same address type was removed and
304                                    // added we need to cleanup
305                                    CompareResult<LinkAddress> car =
306                                        result.oldLp.compareAddresses(result.newLp);
307                                    if (DBG) {
308                                        log("onDataStateChanged: oldLp=" + result.oldLp +
309                                                " newLp=" + result.newLp + " car=" + car);
310                                    }
311                                    boolean needToClean = false;
312                                    for (LinkAddress added : car.added) {
313                                        for (LinkAddress removed : car.removed) {
314                                            if (NetworkUtils.addressTypeMatches(
315                                                    removed.getAddress(),
316                                                    added.getAddress())) {
317                                                needToClean = true;
318                                                break;
319                                            }
320                                        }
321                                    }
322                                    if (needToClean) {
323                                        if (DBG) {
324                                            log("onDataStateChanged: addr change," +
325                                                    " cleanup apns=" + dc.mApnContexts +
326                                                    " oldLp=" + result.oldLp +
327                                                    " newLp=" + result.newLp);
328                                        }
329                                        apnsToCleanup.addAll(dc.mApnContexts.keySet());
330                                    } else {
331                                        if (DBG) log("onDataStateChanged: simple change");
332
333                                        for (ApnContext apnContext : dc.mApnContexts.keySet()) {
334                                             mPhone.notifyDataConnection(
335                                                 PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
336                                                 apnContext.getApnType());
337                                        }
338                                    }
339                                } else {
340                                    if (DBG) {
341                                        log("onDataStateChanged: no changes");
342                                    }
343                                }
344                            } else {
345                                apnsToCleanup.addAll(dc.mApnContexts.keySet());
346                                if (DBG) {
347                                    log("onDataStateChanged: interface change, cleanup apns="
348                                            + dc.mApnContexts);
349                                }
350                            }
351                        }
352                    }
353                }
354
355                if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_UP) {
356                    isAnyDataCallActive = true;
357                }
358                if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT) {
359                    isAnyDataCallDormant = true;
360                }
361            }
362
363            if (isAnyDataCallDormant && !isAnyDataCallActive) {
364                // There is no way to indicate link activity per APN right now. So
365                // Link Activity will be considered dormant only when all data calls
366                // are dormant.
367                // If a single data call is in dormant state and none of the data
368                // calls are active broadcast overall link state as dormant.
369                if (DBG) {
370                    log("onDataStateChanged: Data Activity updated to DORMANT. stopNetStatePoll");
371                }
372                mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT);
373            } else {
374                if (DBG) {
375                    log("onDataStateChanged: Data Activity updated to NONE. " +
376                            "isAnyDataCallActive = " + isAnyDataCallActive +
377                            " isAnyDataCallDormant = " + isAnyDataCallDormant);
378                }
379                if (isAnyDataCallActive) {
380                    mDct.sendStartNetStatPoll(DctConstants.Activity.NONE);
381                }
382            }
383
384            if (DBG) {
385                lr("onDataStateChanged: dcsToRetry=" + dcsToRetry
386                        + " apnsToCleanup=" + apnsToCleanup);
387            }
388
389            // Cleanup connections that have changed
390            for (ApnContext apnContext : apnsToCleanup) {
391               mDct.sendCleanUpConnection(true, apnContext);
392            }
393
394            // Retry connections that have disappeared
395            for (DataConnection dc : dcsToRetry) {
396                if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag);
397                dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag);
398            }
399
400            if (VDBG) log("onDataStateChanged: X");
401        }
402    }
403
404    /**
405     * lr is short name for logAndAddLogRec
406     * @param s
407     */
408    private void lr(String s) {
409        logAndAddLogRec(s);
410    }
411
412    @Override
413    protected void log(String s) {
414        Rlog.d(getName(), s);
415    }
416
417    @Override
418    protected void loge(String s) {
419        Rlog.e(getName(), s);
420    }
421
422    /**
423     * @return the string for msg.what as our info.
424     */
425    @Override
426    protected String getWhatToString(int what) {
427        String info = null;
428        info = DataConnection.cmdToString(what);
429        if (info == null) {
430            info = DcAsyncChannel.cmdToString(what);
431        }
432        return info;
433    }
434
435    @Override
436    public String toString() {
437        return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
438    }
439
440    @Override
441    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
442        super.dump(fd, pw, args);
443        pw.println(" mPhone=" + mPhone);
444        pw.println(" mDcListAll=" + mDcListAll);
445        pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
446    }
447}
448