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 
17 package com.android.bandwidthtest;
18 
19 import android.net.NetworkInfo.State;
20 import android.util.Log;
21 
22 import java.util.List;
23 import java.util.ArrayList;
24 
25 /**
26  * Data structure to keep track of the network state transitions.
27  */
28 public 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 
NetworkState()41     public NetworkState() {
42         mStateDepository = new ArrayList<State>();
43         mTransitionDirection = StateTransitionDirection.DO_NOTHING;
44         mTransitionTarget = State.UNKNOWN;
45     }
46 
NetworkState(State currentState)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      */
resetNetworkState()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      */
setStateTransitionCriteria(State initState, StateTransitionDirection transitionDir, State targetState)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      */
recordState(State currentState)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      */
validateStateTransition()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      */
validateNetworkStates()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      */
validateNetworkDisconnection()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      */
validateNetworkConnection()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      */
getTransitionStates()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      */
getFailureReason()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      */
printStates()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
toString()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