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