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