javaandroidreact-nativeaccountmanagerandroid-authenticator

App freezing, breaking and forcing to close by "Uncaught remote exception!"


I've been developing a socket application by using react-native and socket.io.

I use AccountManager and Authenticator to store the user credential on Android.

In Android, I listen to error (aka onError) event.

When the socket connection is closed unexpectedly error event is fired.

And if the error contains EngineIOException: websocket error text I start a handler postDelayed to connect to the socket server again.

Unfortunately the app freezes when running postDelayed code. Then a dialog windows appearances that includes App isn't responding - Do you want to close it? - [Wait] [OK].

If I press [OK] button, another message box appearances in few seconds.

I tried to investigate the whole day but I couldn't find a solution.

Is there any chance to overcome this issue?

additional info

ADB logcat shows the below messages after the app broke.

I/WindowState(  394): WIN DEATH: Window{52a4aef8 u0 com.rnnativemodules/com.rnnativemodules.MainActivity}
D/dalvikvm(  394): GC_CONCURRENT freed 1912K, 26% free 10997K/14820K, paused 2ms+2ms, total 104ms
W/EGL_genymotion(  690): eglSurfaceAttrib not implemented
D/MobileDataStateTracker(  394): default: setPolicyDataEnable(enabled=true)
I/qtaguid (  394): Failed write_ctrl(s 0 10083) res=-1 errno=1
W/NetworkManagementSocketTagger(  394): setKernelCountSet(10083, 0) failed with errno -1
I/qtaguid (  394): Failed write_ctrl(s 1 10023) res=-1 errno=1
W/NetworkManagementSocketTagger(  394): setKernelCountSet(10023, 1) failed with errno -1
W/InputMethodManagerService(  394): Got RemoteException sending setActive(false) notification to pid 16331 uid 10083
D/Sensors (  394): Client connection accepted (187)
E/Genymotion(  394): Could not open '/sys/class/power_supply/genymotion_fake_path/present'
D/dalvikvm(16391): Late-enabling CheckJNI
I/ActivityManager(  394): Start proc com.rnnativemodules for service com.rnnativemodules/com.customemodule.AuthenticatorService: pid=16391 uid=10083 gids={50083, 3003, 1015, 1028}
W/dalvikvm(16391): Exception Ljava/lang/RuntimeException; thrown while initializing Lcom/facebook/react/bridge/ReactBridge;
W/dalvikvm(16391): Exception Ljava/lang/ExceptionInInitializerError; thrown while initializing Lcom/facebook/react/bridge/NativeMap;
E/JavaBinder(16391): *** Uncaught remote exception!  (Exceptions are not yet supported across processes.)
E/JavaBinder(16391): java.lang.ExceptionInInitializerError
E/JavaBinder(16391):    at com.facebook.react.bridge.NativeMap.<clinit>(NativeMap.java:22)
E/JavaBinder(16391):    at com.customemodule.APIServerAuthenticate.signIn(APIServerAuthenticate.java:56)
E/JavaBinder(16391):    at com.customemodule.Authenticator.getAuthToken(Authenticator.java:79)
E/JavaBinder(16391):    at android.accounts.AbstractAccountAuthenticator$Transport.getAuthToken(AbstractAccountAuthenticator.java:196)
E/JavaBinder(16391):    at android.accounts.IAccountAuthenticator$Stub.onTransact(IAccountAuthenticator.java:113)
E/JavaBinder(16391):    at android.os.Binder.execTransact(Binder.java:388)
E/JavaBinder(16391):    at dalvik.system.NativeStart.run(Native Method)
E/JavaBinder(16391): Caused by: java.lang.RuntimeException: SoLoader.init() not yet called
E/JavaBinder(16391):    at com.facebook.soloader.SoLoader.assertInitialized(SoLoader.java:234)
E/JavaBinder(16391):    at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:169)
E/JavaBinder(16391):    at com.facebook.react.bridge.ReactBridge.staticInit(ReactBridge.java:39)
E/JavaBinder(16391):    at com.facebook.react.bridge.ReactBridge.<clinit>(ReactBridge.java:31)
E/JavaBinder(16391):    ... 7 more
W/dalvikvm(16391): threadid=9: thread exiting with uncaught exception (group=0xa4bff648)
E/AndroidRuntime(16391): FATAL EXCEPTION: Binder_1
E/AndroidRuntime(16391): java.lang.ExceptionInInitializerError
E/AndroidRuntime(16391):        at com.facebook.react.bridge.NativeMap.<clinit>(NativeMap.java:22)
E/AndroidRuntime(16391):        at com.customemodule.APIServerAuthenticate.signIn(APIServerAuthenticate.java:56)
E/AndroidRuntime(16391):        at com.customemodule.Authenticator.getAuthToken(Authenticator.java:79)
E/AndroidRuntime(16391):        at android.accounts.AbstractAccountAuthenticator$Transport.getAuthToken(AbstractAccountAuthenticator.java:196)
E/AndroidRuntime(16391):        at android.accounts.IAccountAuthenticator$Stub.onTransact(IAccountAuthenticator.java:113)
E/AndroidRuntime(16391):        at android.os.Binder.execTransact(Binder.java:388)
E/AndroidRuntime(16391):        at dalvik.system.NativeStart.run(Native Method)
E/AndroidRuntime(16391): Caused by: java.lang.RuntimeException: SoLoader.init() not yet called
E/AndroidRuntime(16391):        at com.facebook.soloader.SoLoader.assertInitialized(SoLoader.java:234)
E/AndroidRuntime(16391):        at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:169)
E/AndroidRuntime(16391):        at com.facebook.react.bridge.ReactBridge.staticInit(ReactBridge.java:39)
E/AndroidRuntime(16391):        at com.facebook.react.bridge.ReactBridge.<clinit>(ReactBridge.java:31)
E/AndroidRuntime(16391):        ... 7 more

My class like the following:

public class SocketIO {

    final String TAG = "!NativeModules";
    final String TAG2 = this.getClass().getSimpleName();
    final String ERR_TOKEN = "ERR_TOKEN";
    final String ERR_NO_AUTH_HEADER = "ERR_NO_AUTH_HEADER";

    private final String SERVER_URL = "http://192.168.1.25:3443";
    private final String mAccountType = "api.awesome.mobile";
    private final String mAuthTokenType = "FULL_ACCESS";
    private final String[] mAuthToken = new String[1];

    Boolean areEventsRegistered = false;

    // TODO: mActivity and mContext must be changed with Service's Activity and Context
    private Activity mActivity;
    private ReactApplicationContext mContext;

    public SocketIO(ReactApplicationContext context) {
        this.mContext = context;
    }

    private Socket mSocket;
    {
            try {
                    IO.Options opts = new IO.Options();
                    opts.reconnectionDelay = 2000;
                    opts.reconnectionDelayMax = 14000;
                    // opts.randomizationFactor = 0.8;
                    mSocket = IO.socket(SERVER_URL, opts);
            } catch (URISyntaxException e) {
                    throw new RuntimeException(e);
            }
    }

    public Socket getSocket() {
        return mSocket;
    }

    /**
     * This method is used for to prevent register the same Listener multiple times.
     */
    private void registerEvents () {
        if (areEventsRegistered) {
            return;
        }

        // Setting the request header!
        mSocket.io().on(Manager.EVENT_TRANSPORT, onTransport);

        mSocket.on(Socket.EVENT_CONNECT, onConnect);
        mSocket.on(Socket.EVENT_DISCONNECT, onDisconnect);
        mSocket.on(Socket.EVENT_ERROR, onError);
        mSocket.on(Socket.EVENT_CONNECT_ERROR, onConnectError);
        mSocket.on(Socket.EVENT_RECONNECT, onReconnect);
        mSocket.on(Socket.EVENT_RECONNECT_ERROR, onReconnectError);

        areEventsRegistered = true;
    }



    private void deregisterEvents () {
        if (!areEventsRegistered) {
            return;
        }

        mSocket.io().off(Manager.EVENT_TRANSPORT, onTransport);

        mSocket.off(Socket.EVENT_CONNECT, onConnect);
        mSocket.off(Socket.EVENT_DISCONNECT, onDisconnect);
        mSocket.off(Socket.EVENT_ERROR, onError);
        mSocket.off(Socket.EVENT_CONNECT_ERROR, onConnectError);
        mSocket.off(Socket.EVENT_RECONNECT, onReconnect);
        mSocket.off(Socket.EVENT_RECONNECT_ERROR, onReconnectError);

        areEventsRegistered = false;
    }



    public void connect (Activity activity) {

        mActivity = activity;

        WritableMap results = getAuthToken(mAccountType, mAuthTokenType);
        if (results.hasKey(AccountManager.KEY_AUTHTOKEN)) {
            mAuthToken[0] = results.getString(AccountManager.KEY_AUTHTOKEN);
            registerEvents();
            mSocket.connect();
        } else {
            /**
             * ERROR HANDLING
             */
            String errorCode = results.getString(AccountManager.KEY_ERROR_CODE);
            String errorMessage = results.getString(AccountManager.KEY_ERROR_MESSAGE);
            Log.d(TAG, TAG2 + " ---> connect - errorCode: " + errorCode);
            /**
             * If the error code E_FAILED_TO_CONNECT it means the server is down or cannot
             * be reached at the moment. Try again after 30 seconds.
             */
            if (errorCode.equals(APIServerAuthenticate.ERROR_FAILED_TO_CONNECT_CODE)) {
                Handler handler = new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        connect(mActivity);
                    }
                }, 30000);
            }

        }
    }



    public void disconnect (final Activity activity) {
        mActivity = activity;
        deregisterEvents();
        mSocket.disconnect();
    }



    private Emitter.Listener onTransport = new Emitter.Listener() {
    @Override
    public void call(Object... args) {
      Transport transport = (Transport)args[0];
      transport.on(Transport.EVENT_REQUEST_HEADERS, new Emitter.Listener() {
        @Override
        public void call(Object... args) {
          @SuppressWarnings("unchecked")
          Map<String, List<String>> headers = (Map<String, List<String>>)args[0];
                    if (mAuthToken[0] != null) {
                        String bearer = "Bearer " + mAuthToken[0];
                        headers.put("Authorization", Arrays.asList(bearer));
                    }
        }
      }).on(Transport.EVENT_RESPONSE_HEADERS, new Emitter.Listener() {
        @Override
        public void call(Object... args) {
        }
      });
    }
    };



    private Emitter.Listener onConnect = new Emitter.Listener() {
        @Override
        public void call(final Object... args) {
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {

                }
            });
        }
    };


    private Emitter.Listener onError = new Emitter.Listener() {
        @Override
        public void call(final Object... args) {
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    // Important!
                    disconnect(mActivity);

                    String error = args[0].toString();

                    if (error.equals(ERR_TOKEN)) {
                        /**
                         * If the token sent is invalid the auth server returns ERR_TOKEN error.
                         * Because of that remove the current authtoken from AccountManager cache.
                         * Then try to connect again.
                         */
                        if (invalidateAuthToken(mAccountType, mAuthTokenType)) {
                            connect(mActivity);
                        }
                    }
                    else if (error.contains("EngineIOException: websocket error")) {
                        /**
                         * If the socket connection is closed unexpectedly.
                         * Socket returns an error as "io.socket.engineio.client.EngineIOException:
                         * websocket error". Try again to connect to the server after 30 seconds.
                         */
                        Handler handler = new Handler();
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                connect(mActivity);
                            }
                        }, 30000);
                        return;
                    }
                }
            });
        }
    };


    private Emitter.Listener onConnectError = new Emitter.Listener() {
        @Override
        public void call(final Object... args) {
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {

                }
            });
        }
    };



    private Emitter.Listener onReconnect = new Emitter.Listener() {
        @Override
        public void call(final Object... args) {
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                }
            });
        }
    };



    private Emitter.Listener onReconnectError = new Emitter.Listener() {
        @Override
        public void call(final Object... args) {
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                }
            });
        }
    };



    /**
     * Get the authorization token from the account manager
     * @param accountType
     * @param authTokenType
     */
    public WritableMap getAuthToken(String accountType, String authTokenType) {

        AccountManager mAccountManager = AccountManager.get(mContext);

        // Finished flag
        final boolean[] isFinished = new boolean[1];
        final WritableMap response = new WritableNativeMap();
        final Account availableAccounts[] = mAccountManager.getAccountsByType(accountType);

        if (availableAccounts.length == 0) {
            response.putString(AccountManager.KEY_ERROR_CODE, AccountManagerModule.ERROR_NO_ACCOUNT_CODE);
            response.putString(AccountManager.KEY_ERROR_MESSAGE, AccountManagerModule.ERROR_NO_ACCOUNT_MESSAGE);
            isFinished[0] = true;
        } else {
            final Account account = availableAccounts[0];
            // TODO: activity below must be changed with service activity `getCurrentActivity()``
            final AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(account, authTokenType, null, mActivity, null, null);

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Bundle results = future.getResult();
                        final String authToken = results.getString(AccountManager.KEY_AUTHTOKEN);

                        // If no error message
                        if (results.getString(AccountManager.KEY_ERROR_MESSAGE) == null) {
                            if (authToken == null) {
                                response.putString(AccountManager.KEY_ERROR_CODE, AccountManagerModule.ERROR_NO_TOKEN_CODE);
                                response.putString(AccountManager.KEY_ERROR_MESSAGE, AccountManagerModule.ERROR_NO_TOKEN_MESSAGE);
                                isFinished[0] = true;
                            } else {
                                response.putString(AccountManager.KEY_AUTHTOKEN, results.getString(AccountManager.KEY_AUTHTOKEN));
                                isFinished[0] = true;
                            }
                        } else {
                            response.putString(AccountManager.KEY_ERROR_CODE, results.getString(AccountManager.KEY_ERROR_CODE));
                            response.putString(AccountManager.KEY_ERROR_MESSAGE, results.getString(AccountManager.KEY_ERROR_MESSAGE));
                            isFinished[0] = true;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        response.putString(AccountManager.KEY_ERROR_CODE, AccountManagerModule.ERROR_EXCEPTION_CODE);
                        response.putString(AccountManager.KEY_ERROR_MESSAGE, e.getMessage());
                        isFinished[0] = true;
                    }
                }
            }).start();

        }

        //wait for response
        while (isFinished[0] == false) {
            try{
                Thread.sleep(150);
            } catch (Exception e) {}
        }

        return response;

    }


    /**
     * Removes an auth token from the AccountManager's cache. Does nothing if the auth token is not currently in the cache.
     * Applications must call this method when the auth token is found to have expired or otherwise become invalid for
     * authenticating requests. The AccountManager does not validate or expire cached auth tokens otherwise.
     *
     * @param accountType
     * @param authTokenType
     */
    public boolean invalidateAuthToken(String accountType, String authTokenType) {

        final AccountManager mAccountManager = AccountManager.get(mContext);

        // Finished flag
        final boolean[] isFinished = new boolean[1];
        final boolean[] response = new boolean[1];
        response[0] = false;

        // getting the first relevant account!
        Account accounts[] = mAccountManager.getAccountsByType(accountType);

        if(accounts.length == 0) {
            // throw new Exception(AccountManagerModule.ERROR_NO_ACCOUNT_MESSAGE);
            Log.d(TAG, TAG2 + " ! " + AccountManagerModule.ERROR_NO_ACCOUNT_MESSAGE);
        } else {
            final Account account = accounts[0];
            final AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(account, authTokenType, null, mActivity, null,null);

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Bundle bundle = future.getResult();
                        final String authtoken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
                        mAccountManager.invalidateAuthToken(account.type, authtoken);
                        response[0] = true;
                        isFinished[0] = true;
                    } catch (Exception e) {
                        e.printStackTrace();
                        isFinished[0] = true;
                    }
                }
            }).start();
        }

        //wait for finishing the ops.
        while (isFinished[0] == false) {
            try{
                Thread.sleep(100);
            } catch (Exception e) {}
        }

        return response[0];

    }

}

Solution

  • When starting the code above with a user interaction new Thread or AsynkTask works properly together with while loop which is used for to sleep the Thread.

    However, when running code by a system interaction the App is frozen. Not responding.

    My workaround was that I move the code (in this case getAuthToken() content) -that has async task or that creates a new thread- into the related method. Then I removed while loop to wait for async result. I gave direction to my app in the async part.

    It works for me.