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