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