1/*
2 * Copyright (C) 2011 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.bandwidthtest;
18
19import android.net.NetworkInfo.State;
20import android.util.Log;
21
22import java.util.List;
23import java.util.ArrayList;
24
25/**
26 * Data structure to keep track of the network state transitions.
27 */
28public class NetworkState {
29    /**
30     * Desired direction of state transition.
31     */
32    public enum StateTransitionDirection {
33        TO_DISCONNECTION, TO_CONNECTION, DO_NOTHING
34    }
35    private final String LOG_TAG = "NetworkState";
36    private List<State> mStateDepository;
37    private State mTransitionTarget;
38    private StateTransitionDirection mTransitionDirection;
39    private String mReason = null;         // record mReason of state transition failure
40
41    public NetworkState() {
42        mStateDepository = new ArrayList<State>();
43        mTransitionDirection = StateTransitionDirection.DO_NOTHING;
44        mTransitionTarget = State.UNKNOWN;
45    }
46
47    public NetworkState(State currentState) {
48        mStateDepository = new ArrayList<State>();
49        mStateDepository.add(currentState);
50        mTransitionDirection = StateTransitionDirection.DO_NOTHING;
51        mTransitionTarget = State.UNKNOWN;
52    }
53
54    /**
55     * Reinitialize the network state
56     */
57    public void resetNetworkState() {
58        mStateDepository.clear();
59        mTransitionDirection = StateTransitionDirection.DO_NOTHING;
60        mTransitionTarget = State.UNKNOWN;
61    }
62
63    /**
64     * Set the transition criteria
65     * @param initState initial {@link State}
66     * @param transitionDir explicit {@link StateTransitionDirection}
67     * @param targetState desired {@link State}
68     */
69    public void setStateTransitionCriteria(State initState, StateTransitionDirection transitionDir,
70            State targetState) {
71        if (!mStateDepository.isEmpty()) {
72            mStateDepository.clear();
73        }
74        mStateDepository.add(initState);
75        mTransitionDirection = transitionDir;
76        mTransitionTarget = targetState;
77        Log.v(LOG_TAG, "setStateTransitionCriteria: " + printStates());
78    }
79
80    /**
81     * Record the current state of the network
82     * @param currentState  the current {@link State}
83     */
84    public void recordState(State currentState) {
85        mStateDepository.add(currentState);
86    }
87
88    /**
89     * Verify the state transition
90     * @return true if the requested transition completed successfully.
91     */
92    public boolean validateStateTransition() {
93        Log.v(LOG_TAG, String.format("Print state depository: %s", printStates()));
94        switch (mTransitionDirection) {
95            case DO_NOTHING:
96                Log.v(LOG_TAG, "No direction requested, verifying network states");
97                return validateNetworkStates();
98            case TO_CONNECTION:
99                Log.v(LOG_TAG, "Transition to CONNECTED");
100                return validateNetworkConnection();
101            case TO_DISCONNECTION:
102                Log.v(LOG_TAG, "Transition to DISCONNECTED");
103                return validateNetworkDisconnection();
104            default:
105                Log.e(LOG_TAG, "Invalid transition direction.");
106                return false;
107        }
108    }
109
110    /**
111     * Verify that network states are valid
112     * @return false if any of the states are invalid
113     */
114    private boolean validateNetworkStates() {
115        if (mStateDepository.isEmpty()) {
116            Log.v(LOG_TAG, "no state is recorded");
117            mReason = "no state is recorded.";
118            return false;
119        } else if (mStateDepository.size() > 1) {
120            Log.v(LOG_TAG, "no broadcast is expected, instead broadcast is probably received");
121            mReason = "no broadcast is expected, instead broadcast is probably received";
122            return false;
123        } else if (mStateDepository.get(0) != mTransitionTarget) {
124            Log.v(LOG_TAG, String.format("%s is expected, but it is %s",
125                    mTransitionTarget.toString(),
126                    mStateDepository.get(0).toString()));
127            mReason = String.format("%s is expected, but it is %s",
128                    mTransitionTarget.toString(),
129                    mStateDepository.get(0).toString());
130            return false;
131        }
132        return true;
133    }
134
135    /**
136     * Verify the network state to disconnection
137     * @return false if any of the state transitions were not valid
138     */
139    private boolean validateNetworkDisconnection() {
140        // Transition from CONNECTED -> DISCONNECTED: CONNECTED->DISCONNECTING->DISCONNECTED
141        StringBuffer str = new StringBuffer ("States: ");
142        str.append(printStates());
143        if (mStateDepository.get(0) != State.CONNECTED) {
144            str.append(String.format(" Initial state should be CONNECTED, but it is %s.",
145                    mStateDepository.get(0)));
146            mReason = str.toString();
147            return false;
148        }
149        State lastState = mStateDepository.get(mStateDepository.size() - 1);
150        if ( lastState != mTransitionTarget) {
151            str.append(String.format(" Last state should be DISCONNECTED, but it is %s",
152                    lastState));
153            mReason = str.toString();
154            return false;
155        }
156        for (int i = 1; i < mStateDepository.size() - 1; i++) {
157            State preState = mStateDepository.get(i-1);
158            State curState = mStateDepository.get(i);
159            if ((preState == State.CONNECTED) && ((curState == State.DISCONNECTING) ||
160                    (curState == State.DISCONNECTED))) {
161                continue;
162            } else if ((preState == State.DISCONNECTING) && (curState == State.DISCONNECTED)) {
163                continue;
164            } else if ((preState == State.DISCONNECTED) && (curState == State.DISCONNECTED)) {
165                continue;
166            } else {
167                str.append(String.format(" Transition state from %s to %s is not valid",
168                        preState.toString(), curState.toString()));
169                mReason = str.toString();
170                return false;
171            }
172        }
173        mReason = str.toString();
174        return true;
175    }
176
177    /**
178     * Verify the network state to connection
179     * @return false if any of the state transitions were not valid
180     */
181    private boolean validateNetworkConnection() {
182        StringBuffer str = new StringBuffer("States ");
183        str.append(printStates());
184        if (mStateDepository.get(0) != State.DISCONNECTED) {
185            str.append(String.format(" Initial state should be DISCONNECTED, but it is %s.",
186                    mStateDepository.get(0)));
187            mReason = str.toString();
188            return false;
189        }
190        State lastState = mStateDepository.get(mStateDepository.size() - 1);
191        if ( lastState != mTransitionTarget) {
192            str.append(String.format(" Last state should be %s, but it is %s", mTransitionTarget,
193                    lastState));
194            mReason = str.toString();
195            return false;
196        }
197        for (int i = 1; i < mStateDepository.size(); i++) {
198            State preState = mStateDepository.get(i-1);
199            State curState = mStateDepository.get(i);
200            if ((preState == State.DISCONNECTED) && ((curState == State.CONNECTING) ||
201                    (curState == State.CONNECTED) || (curState == State.DISCONNECTED))) {
202                continue;
203            } else if ((preState == State.CONNECTING) && (curState == State.CONNECTED)) {
204                continue;
205            } else if ((preState == State.CONNECTED) && (curState == State.CONNECTED)) {
206                continue;
207            } else {
208                str.append(String.format(" Transition state from %s to %s is not valid.",
209                        preState.toString(), curState.toString()));
210                mReason = str.toString();
211                return false;
212            }
213        }
214        mReason = str.toString();
215        return true;
216    }
217
218    /**
219     * Fetch the different network state transitions
220     * @return {@link List} of {@link State}
221     */
222    public List<State> getTransitionStates() {
223        return mStateDepository;
224    }
225
226    /**
227     * Fetch the reason for network state transition failure
228     * @return the {@link String} for the failure
229     */
230    public String getFailureReason() {
231        return mReason;
232    }
233
234    /**
235     * Print the network state
236     * @return {@link String} representation of the network state
237     */
238    public String printStates() {
239        StringBuilder stateBuilder = new StringBuilder();
240        for (int i = 0; i < mStateDepository.size(); i++) {
241            stateBuilder.append(" ").append(mStateDepository.get(i).toString()).append("->");
242        }
243        return stateBuilder.toString();
244    }
245
246    /**
247     * {@inheritDoc}
248     */
249    @Override
250    public String toString() {
251        StringBuilder builder = new StringBuilder();
252        builder.append("mTransitionDirection: ").append(mTransitionDirection.toString()).
253        append("; ").append("states:").
254        append(printStates()).append("; ");
255        return builder.toString();
256    }
257}
258