android: Change state handling to display errors occurring while the app is hidden
authorTobias Brunner <tobias@strongswan.org>
Fri, 20 Sep 2013 13:07:41 +0000 (15:07 +0200)
committerTobias Brunner <tobias@strongswan.org>
Mon, 23 Sep 2013 10:01:43 +0000 (12:01 +0200)
A new connection ID allows listeners to track which errors they have
already shown to the user or were already dismissed by the user.

This was necessary because the state fragment is now unregistered from
state changes when it is not shown.

src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java
src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java
src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java

index 305ee80..853de56 100644 (file)
@@ -190,7 +190,6 @@ public class CharonVpnService extends VpnService implements Runnable
                                        stopCurrentConnection();
                                        if (mNextProfile == null)
                                        {
-                                               setProfile(null);
                                                setState(State.DISABLED);
                                                if (mTerminate)
                                                {
@@ -207,10 +206,7 @@ public class CharonVpnService extends VpnService implements Runnable
                                                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());
@@ -258,17 +254,18 @@ public class CharonVpnService extends VpnService implements Runnable
        }
 
        /**
-        * 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);
                        }
                }
        }
@@ -337,9 +334,9 @@ public class CharonVpnService extends VpnService implements Runnable
                {
                        if (mService != null)
                        {
-                               mService.setError(error);
                                if (!mIsDisconnecting)
                                {
+                                       mService.setError(error);
                                        mService.disconnect();
                                }
                        }
index 2c530ba..3b75a8f 100644 (file)
@@ -36,6 +36,7 @@ public class VpnStateService extends Service
 {
        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;
@@ -133,6 +134,19 @@ public class VpnStateService extends Service
        }
 
        /**
+        * 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.
         *
         * @return state
@@ -223,21 +237,26 @@ public class VpnStateService extends Service
        }
 
        /**
-        * 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;
                        }
                });
        }
index 88f8df3..d1394f6 100644 (file)
@@ -49,9 +49,8 @@ import android.widget.TextView;
 
 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;
@@ -59,11 +58,9 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
        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
@@ -91,13 +88,12 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
                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);
                }
        }
 
@@ -106,9 +102,8 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
        {
                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
@@ -179,37 +174,27 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
 
        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)
                {
@@ -239,19 +224,11 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
                }
        }
 
-       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 */
@@ -332,8 +309,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
 
        private void clearError()
        {
-               mError = ErrorState.NO_ERROR;
-               mImcState = ImcState.UNKNOWN;
+               mDismissedConnectionID = mErrorConnectionID;
                updateView();
        }
 
@@ -371,7 +347,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
        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())