Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moto G5 Plus can't connect via Socket to a local server

I have an application where the smartphone must connect via SSLSocket to a local server. I tested my app on 5 different smartphones: Moto G2 (6.0), Redmi 3S (6.0.1), LG K5 (6.0), Moto G5 Plus (7.1.1) and OnePlus 5 (8.0). The Moto G5 Plus was the only one to show this problem.

This is the line that causes the problematic behaviour. All tests where done on the same network.

socket = (SSLSocket) sslContext.getSocketFactory().createSocket(serverAddress, serverPort);

Is there any known problem with Moto G5 Plus or with Android 7+ around this behaviour?

EDIT: Some more tests are leading to an idea of Android system trying to force the Socket to connect through mobile network when identifying that WiFi interface is connected, but with no internet. Is there any way to enforce the Socket to use WiFi instead of mobile network?

like image 381
Minoru Avatar asked Mar 26 '18 12:03

Minoru


2 Answers

Disclaimer: I didn't test this so I'm really not sure if it works.

The Network class has a bind(Socket) method, maybe you could find the wifi network and then bind it to your socket. From the doc it seems that this is what you need, it says:

/**
 * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
 * will be sent on this {@code Network}, irrespective of any process-wide network binding set by
 * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
 */

The Socket shouldn't be connected before binding to the network, so I think you should create it with socketFactory.createSocket() and connect it only after the binding.

So, you should first find your Network (Kotlin):

val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val wifiNetwork = connectivityManager.allNetworks.firstOrNull {
    val info = connectivityManager.getNetworkInfo(it)
    info.type == ConnectivityManager.TYPE_WIFI
}

or (Java)

ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
Network wifiNetwork = null;
for(Network network : connectivityManager.getAllNetworks()){
    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
    if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI){
        wifiNetwork = network;
        break;
    }
}

Then bind it to the Socket and finally connect (Kotlin):

wifiNetwork?.bindSocket(socket)
val socketAddress = InetSocketAddress(hostname, port)
socket.connect(socketAddress)

or (Java)

if(wifiNetwork != null){
    wifiNetwork.bindSocket(socket);
}
InetSocketAddress socketAddress = InetSocketAddress(hostName, port);
socket.connect(socketAddress);

Note, it needs ACCESS_NETWORK_STATE permission

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
like image 174
lelloman Avatar answered Nov 11 '22 04:11

lelloman


i hope it helps you,i just found your solution on github. for further details and for official link,please check this out, i think this can be helpful to you. if it is not the answer,please ignore this answer.

We are using AsyncTask to avoid StrictMode fatal error for network access ( Look in references ). The StrictMode policy is simply forbidding us to affect on UI Thread.

   /* AsyncTask class which manages connection with server app and is sending shutdown command.
   */
public class ShutdownAsyncTask extends AsyncTask<String, String, TCPClient> {

    private static final String     COMMAND     = "shutdown -s"      ;
    private              TCPClient  tcpClient                        ;
    private              Handler    mHandler                         ;
    private static final String     TAG         = "ShutdownAsyncTask";

    /**
     * ShutdownAsyncTask constructor with handler passed as argument. The UI is updated via handler.
     * In doInBackground(...) method, the handler is passed to TCPClient object.
     * @param mHandler Handler object that is retrieved from MainActivity class and passed to TCPClient
     *                 class for sending messages and updating UI.
     */
    public ShutdownAsyncTask(Handler mHandler){
        this.mHandler = mHandler;
    }

    /**
     * Overriden method from AsyncTask class. There the TCPClient object is created.
     * @param params From MainActivity class empty string is passed.
     * @return TCPClient object for closing it in onPostExecute method.
     */
    @Override
    protected TCPClient doInBackground(String... params) {
        Log.d(TAG, "In do in background");

        try{
            tcpClient = new TCPClient(mHandler,
                                      COMMAND,
                                      "192.168.1.1",
                                      new TCPClient.MessageCallback() {
                @Override
                public void callbackMessageReceiver(String message) {
                    publishProgress(message);
                }
            });

        }catch (NullPointerException e){
            Log.d(TAG, "Caught null pointer exception");
            e.printStackTrace();
        }
        tcpClient.run();
        return null;
    }   

In this AsyncTask we are creating TCPClient object ( explained below ). In the TCPClient constructor we are passing Handler object for changing the UI, COMMAND - the String with the "shutdown -s" command for shutting down the computer, IP number - the servers ip number; Callback object - when we are getting servers response, the callback method 'messageCallbackReceiver' is starting 'publishProgress' method, which is publishing progress to 'onProgressUpdate' AsyncTask's method.

        /**
         * Overriden method from AsyncTask class. Here we're checking if server answered properly.
         * @param values If "restart" message came, the client is stopped and computer should be restarted.
         *               Otherwise "wrong" message is sent and 'Error' message is shown in UI.
         */
        @Override
        protected void onProgressUpdate(String... values) {
            super.onProgressUpdate(values);
            Log.d(TAG, "In progress update, values: " + values.toString());
            if(values[0].equals("shutdown")){
                tcpClient.sendMessage(COMMAND);
                tcpClient.stopClient();
                mHandler.sendEmptyMessageDelayed(MainActivity.SHUTDOWN, 2000);

            }else{
                tcpClient.sendMessage("wrong");
                mHandler.sendEmptyMessageDelayed(MainActivity.ERROR, 2000);
                tcpClient.stopClient();
            }
        }

After receiving proper message, we are sending command, or if we received wrong message, we are sending message "wrong" and stopping client. After this we are being transferred to the 'onPostExecute' method:

    @Override
        protected void onPostExecute(TCPClient result){
            super.onPostExecute(result);
            Log.d(TAG, "In on post execute");
            if(result != null && result.isRunning()){
                result.stopClient();
            }
            mHandler.sendEmptyMessageDelayed(MainActivity.SENT, 4000);

        }
    }

So step by step:

->AsyncTask is creating TCPClient object.

->In TCPClient constructor we are passing Handler, Command, IP Number and Callback object.

->When TCPClient begins connection, it sends message "shutdown" to the server.

->When we are receiving message from server, the callback is passing it to 'onProgressUpdate'.

->If the received message ( response from server ) is equal to "shutdown", we are sending COMMAND to server.

->After sending it we are stopping client, which is transferring us to 'onPostExecute' method.

->Meanwhile, the handler is receiving empty messages with 'msg.what' integers defined in MainActivity, which are responsible for updating the GUI.

Example of how the widget UI is updated:

       mHandler = new Handler(){
        public void handleMessage(Message msg) {
            switch(msg.what){
                case SHUTDOWN:
                    Log.d(mTag, "In Handler's shutdown");

                     views     = new RemoteViews(context.getPackageName(), R.layout.activity_main);
                     widget    = new ComponentName(context, MainActivity.class);
                     awManager = AppWidgetManager.getInstance(context);
                                 views.setTextViewText(R.id.state, "Shutting PC...");
                                 awManager.updateAppWidget(widget,views);
                    break;

TCPClient

This class is responsible for maintaining the connection. I will explain it step by step:

In the first step we can see the objects passed from ShutdownAsyncTask and others. Additionally we can see the sendMessage and stopClient methods.

    public class TCPClient {

        private static final String            TAG             = "TCPClient"     ;
        private final        Handler           mHandler                          ;
        private              String            ipNumber, incomingMessage, command;
                             BufferedReader    in                                ;
                             PrintWriter       out                               ;
        private              MessageCallback   listener        = null            ;
        private              boolean           mRun            = false           ;


        /**
         * TCPClient class constructor, which is created in AsyncTasks after the button click.
         * @param mHandler Handler passed as an argument for updating the UI with sent messages
         * @param command  Command passed as an argument, e.g. "shutdown -r" for restarting computer
         * @param ipNumber String retrieved from IpGetter class that is looking for ip number.
         * @param listener Callback interface object
         */
        public TCPClient(Handler mHandler, String command, String ipNumber, MessageCallback listener) {
            this.listener         = listener;
            this.ipNumber         = ipNumber;
            this.command          = command ;
            this.mHandler         = mHandler;
        }

        /**
         * Public method for sending the message via OutputStream object.
         * @param message Message passed as an argument and sent via OutputStream object.
         */
        public void sendMessage(String message){
            if (out != null && !out.checkError()) {
                out.println(message);
                out.flush();
                mHandler.sendEmptyMessageDelayed(MainActivity.SENDING, 1000);
                Log.d(TAG, "Sent Message: " + message);

            }
        }

        /**
         * Public method for stopping the TCPClient object ( and finalizing it after that ) from AsyncTask
         */
        public void stopClient(){
            Log.d(TAG, "Client stopped!");
            mRun = false;
        }

The magic happens here - in 'run()' method. Here we are using 'try-catch' tools for handling exceptions ( server not enabled, ip not proper etc. ). As you can see below, we have infinite while() loop for listening to the incoming messages. We can simply stop it and finalize with 'stopClient()' method ( used in ShutdownAsyncTask's 'onProgressUpdate' method)

    public void run() {

            mRun = true;

            try {
                // Creating InetAddress object from ipNumber passed via constructor from IpGetter class.
                InetAddress serverAddress = InetAddress.getByName(ipNumber);

                Log.d(TAG, "Connecting...");

                /**
                 * Sending empty message with static int value from MainActivity
                 * to update UI ( 'Connecting...' ).
                 *
                 * @see com.example.turnmeoff.MainActivity.CONNECTING
                 */
                mHandler.sendEmptyMessageDelayed(MainActivity.CONNECTING,1000);

                /**
                 * Here the socket is created with hardcoded port.
                 * Also the port is given in IpGetter class.
                 *
                 * @see com.example.turnmeoff.IpGetter
                 */
                Socket socket = new Socket(serverAddress, 4444);


                try {

                    // Create PrintWriter object for sending messages to server.
                    out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);

                    //Create BufferedReader object for receiving messages from server.
                    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                    Log.d(TAG, "In/Out created");

                    //Sending message with command specified by AsyncTask
                    this.sendMessage(command);

                    //
                    mHandler.sendEmptyMessageDelayed(MainActivity.SENDING,2000);

                    //Listen for the incoming messages while mRun = true
                    while (mRun) {
                        incomingMessage = in.readLine();
                        if (incomingMessage != null && listener != null) {

                            /**
                             * Incoming message is passed to MessageCallback object.
                             * Next it is retrieved by AsyncTask and passed to onPublishProgress method.
                             *
                             */
                            listener.callbackMessageReceiver(incomingMessage);

                        }
                        incomingMessage = null;

                    }

                    Log.d(TAG, "Received Message: " +incomingMessage);

                } catch (Exception e) {

                    Log.d(TAG, "Error", e);
                    mHandler.sendEmptyMessageDelayed(MainActivity.ERROR, 2000);

                } finally {

                    out.flush();
                    out.close();
                    in.close();
                    socket.close();
                    mHandler.sendEmptyMessageDelayed(MainActivity.SENT, 3000);
                    Log.d(TAG, "Socket Closed");
                }

            } catch (Exception e) {

                Log.d(TAG, "Error", e);
                mHandler.sendEmptyMessageDelayed(MainActivity.ERROR, 2000);

            }

        }

The last thing in the client is the Callback interface. We have it in the TCPClient class in the end:

    /**
         * Callback Interface for sending received messages to 'onPublishProgress' method in AsyncTask.
         *
         */
        public interface MessageCallback {
            /**
             * Method overriden in AsyncTask 'doInBackground' method while creating the TCPClient object.
             * @param message Received message from server app.
             */
            public void callbackMessageReceiver(String message);
        }
like image 42
Mrunal Chauhan Avatar answered Nov 11 '22 03:11

Mrunal Chauhan