stopCurrentConnection();
if (mNextProfile == null)
{
- setProfile(null);
setState(State.DISABLED);
if (mTerminate)
{
mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias();
mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias();
- setProfile(mCurrentProfile);
- setError(ErrorState.NO_ERROR);
- setState(State.CONNECTING);
- setImcState(ImcState.UNKNOWN);
+ startConnection(mCurrentProfile);
mIsDisconnecting = false;
BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName());
}
/**
- * Update the VPN profile on the state service. Called by the handler thread.
+ * Notify the state service about a new connection attempt.
+ * Called by the handler thread.
*
* @param profile currently active VPN profile
*/
- private void setProfile(VpnProfile profile)
+ private void startConnection(VpnProfile profile)
{
synchronized (mServiceLock)
{
if (mService != null)
{
- mService.setProfile(profile);
+ mService.startConnection(profile);
}
}
}
{
if (mService != null)
{
- mService.setError(error);
if (!mIsDisconnecting)
{
+ mService.setError(error);
mService.disconnect();
}
}
{
private final List<VpnStateListener> mListeners = new ArrayList<VpnStateListener>();
private final IBinder mBinder = new LocalBinder();
+ private long mConnectionID = 0;
private Handler mHandler;
private VpnProfile mProfile;
private State mState = State.DISABLED;
return mProfile;
}
+ /**
+ * Get the current connection ID. May be used to track which state
+ * changes have already been handled.
+ *
+ * Is increased when startConnection() is called.
+ *
+ * @return connection ID
+ */
+ public long getConnectionID()
+ { /* only updated from the main thread so no synchronization needed */
+ return mConnectionID;
+ }
+
/**
* Get the current state.
*
}
/**
- * Set the VPN profile currently active. Listeners are not notified.
+ * Called when a connection is started. Sets the currently active VPN
+ * profile, resets IMC and Error state variables, sets the State to
+ * CONNECTING, increases the connection ID, and notifies all listeners.
*
* May be called from threads other than the main thread.
*
* @param profile current profile
*/
- public void setProfile(final VpnProfile profile)
+ public void startConnection(final VpnProfile profile)
{
- /* even though we don't notify the listeners the update is done from the
- * same handler so updates are predictable for listeners */
- mHandler.post(new Runnable() {
+ notifyListeners(new Callable<Boolean>() {
@Override
- public void run()
+ public Boolean call() throws Exception
{
+ VpnStateService.this.mConnectionID++;
VpnStateService.this.mProfile = profile;
+ VpnStateService.this.mState = State.CONNECTING;
+ VpnStateService.this.mError = ErrorState.NO_ERROR;
+ VpnStateService.this.mImcState = ImcState.UNKNOWN;
+ return true;
}
});
}
public class VpnStateFragment extends Fragment implements VpnStateListener
{
- private static final String KEY_ERROR = "error";
- private static final String KEY_IMC_STATE = "imc_state";
- private static final String KEY_NAME = "name";
+ private static final String KEY_ERROR_CONNECTION_ID = "error_connection_id";
+ private static final String KEY_DISMISSED_CONNECTION_ID = "dismissed_connection_id";
private TextView mProfileNameView;
private TextView mProfileView;
private int stateBaseColor;
private Button mActionButton;
private ProgressDialog mProgressDialog;
- private State mState;
private AlertDialog mErrorDialog;
- private ErrorState mError;
- private ImcState mImcState;
- private String mErrorProfileName;
+ private long mErrorConnectionID;
+ private long mDismissedConnectionID;
private VpnStateService mService;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
context.bindService(new Intent(context, VpnStateService.class),
mServiceConnection, Service.BIND_AUTO_CREATE);
- mError = ErrorState.NO_ERROR;
- mImcState = ImcState.UNKNOWN;
- if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR))
+ mErrorConnectionID = 0;
+ mDismissedConnectionID = 0;
+ if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR_CONNECTION_ID))
{
- mError = (ErrorState)savedInstanceState.getSerializable(KEY_ERROR);
- mImcState = (ImcState)savedInstanceState.getSerializable(KEY_IMC_STATE);
- mErrorProfileName = savedInstanceState.getString(KEY_NAME);
+ mErrorConnectionID = (Long)savedInstanceState.getSerializable(KEY_ERROR_CONNECTION_ID);
+ mDismissedConnectionID = (Long)savedInstanceState.getSerializable(KEY_DISMISSED_CONNECTION_ID);
}
}
{
super.onSaveInstanceState(outState);
- outState.putSerializable(KEY_ERROR, mError);
- outState.putSerializable(KEY_IMC_STATE, mImcState);
- outState.putString(KEY_NAME, mErrorProfileName);
+ outState.putSerializable(KEY_ERROR_CONNECTION_ID, mErrorConnectionID);
+ outState.putSerializable(KEY_DISMISSED_CONNECTION_ID, mDismissedConnectionID);
}
@Override
public void updateView()
{
+ long connectionID = mService.getConnectionID();
+ VpnProfile profile = mService.getProfile();
State state = mService.getState();
- ErrorState error = ErrorState.NO_ERROR;
- ImcState imcState = ImcState.UNKNOWN;
+ ErrorState error = mService.getErrorState();
+ ImcState imcState = mService.getImcState();
String name = "", gateway = "";
- if (state != State.DISABLED)
+ if (profile != null)
{
- VpnProfile profile = mService.getProfile();
- if (profile != null)
- {
- name = profile.getName();
- gateway = profile.getGateway();
- }
- error = mService.getErrorState();
- imcState = mService.getImcState();
+ name = profile.getName();
+ gateway = profile.getGateway();
}
- if (reportError(name, state, error, imcState))
+ if (reportError(connectionID, name, error, imcState))
{
return;
}
- if (state == mState)
- { /* avoid unnecessary updates */
- return;
- }
-
hideProgressDialog();
enableActionButton(false);
mProfileNameView.setText(name);
- mState = state;
switch (state)
{
}
}
- private boolean reportError(String name, State state, ErrorState error, ImcState imcState)
+ private boolean reportError(long connectionID, String name, ErrorState error, ImcState imcState)
{
- if (mError != ErrorState.NO_ERROR)
- { /* we are currently reporting an error which was not yet dismissed */
- error = mError;
- imcState = mImcState;
- name = mErrorProfileName;
- }
- else if (error != ErrorState.NO_ERROR && (state == State.CONNECTING || state == State.CONNECTED))
- { /* while initiating we report errors */
- mError = error;
- mImcState = imcState;
- mErrorProfileName = name;
+ if (connectionID > mDismissedConnectionID)
+ { /* report error if it hasn't been dismissed yet */
+ mErrorConnectionID = connectionID;
}
else
{ /* ignore all other errors */
private void clearError()
{
- mError = ErrorState.NO_ERROR;
- mImcState = ImcState.UNKNOWN;
+ mDismissedConnectionID = mErrorConnectionID;
updateView();
}
private void showErrorDialog(int textid)
{
final List<RemediationInstruction> instructions = mService.getRemediationInstructions();
- final boolean show_instructions = mImcState == ImcState.BLOCK && !instructions.isEmpty();
+ final boolean show_instructions = mService.getImcState() == ImcState.BLOCK && !instructions.isEmpty();
int text = show_instructions ? R.string.show_remediation_instructions : R.string.show_log;
mErrorDialog = new AlertDialog.Builder(getActivity())