de.jeanpierrehotz.messagingapptest.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for de.jeanpierrehotz.messagingapptest.MainActivity.java

Source

/*
 *     Copyright 2016 Jeremy Schiemann, Jean-Pierre Hotz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.jeanpierrehotz.messagingapptest;

import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkRequest;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.CardView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.RelativeLayout;

// See gradle dependencies for following classes
// compile 'com.facebook.rebound:rebound:0.3.8'
// compile 'com.tumblr:backboard:0.1.0'
import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringSystem;
import com.tumblr.backboard.Actor;
import com.tumblr.backboard.MotionProperty;
import com.tumblr.backboard.imitator.SpringImitator;
import com.tumblr.backboard.performer.Performer;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;

import de.jeanpierrehotz.messagingapptest.funuistuff.ColoredSnackbar;
import de.jeanpierrehotz.messagingapptest.messages.Message;
import de.jeanpierrehotz.messagingapptest.messages.MessageAdapter;
import de.jeanpierrehotz.messagingapptest.messages.ReceivedMessage;
import de.jeanpierrehotz.messagingapptest.network.MessageListener;
import de.jeanpierrehotz.messagingapptest.network.MessageSender;

public class MainActivity extends AppCompatActivity {

    /**
     * Das RelativeLayout, das die Einstellungen zeigt
     */
    private RelativeLayout settingsBottomSheetLayout;
    /**
     * Das BottomSheetBehavior, das die Einstellungen zeigt / versteckt.
     */
    private BottomSheetBehavior<RelativeLayout> settingsBottomSheetBehaviour;

    /**
     * Das EditText, in dem der User seinen UserNamen eingeben kann
     */
    private EditText nameTextView;
    /**
     * Der derzeitige UserName
     */
    private String currentName;

    /**
     * Die Liste von Nachrichten, die erstmal nur fr eine Sitzung gespeichert werden
     */
    private ArrayList<Message> mMessages;

    /**
     * Das RecyclerView, das die Nachrichten anzeigt
     */
    private RecyclerView mMessagesView;
    /**
     * Der Adapter fr die Nachrichten
     */
    private MessageAdapter mMessagesAdapter;
    /**
     * Der LayoutManager, der das Layout des RecyclerViews gut macht
     */
    private LinearLayoutManager mLayoutManager;

    /**
     * Der FloatingActionButton, mit dem eine Nachricht gesendet wird
     */
    private FloatingActionButton mSendBtn;
    /**
     * Das EditText, in dem die Nachricht eingegeben werden soll
     */
    private EditText mSendEditText;

    /**
     * Der Socket, der das Handy mit dem Server verbindet, der die Nachrichten verschickt
     */
    private Socket server;
    /**
     * Der MessageSender, der es ermglicht Nachrichten einfach zu versenden
     */
    private MessageSender serverMessageSender;
    /**
     * Der PingListener wartet auf die Nachricht, ob ein Pind erfolgreich war, oder nicht
     */
    private MessageSender.PingListener pingListener = new MessageSender.PingListener() {
        @Override
        public void onConnectionDetected(boolean connected) {
            //          Wir zeigen dem User, ob der Ping erfolgreich war
            if (connected) {
                ColoredSnackbar.make(ContextCompat.getColor(MainActivity.this, R.color.colorConnectionRestored),
                        mMessagesView, getString(R.string.pingresult_connected), Snackbar.LENGTH_SHORT,
                        ContextCompat.getColor(MainActivity.this, R.color.colorConnectionFont)).show();
            } else {
                ColoredSnackbar.make(ContextCompat.getColor(MainActivity.this, R.color.colorConnectionLost),
                        mMessagesView, getString(R.string.pingresult_timedout), Snackbar.LENGTH_SHORT,
                        ContextCompat.getColor(MainActivity.this, R.color.colorConnectionFont)).show();
            }
        }
    };
    /**
     * Der MessageListener, der auf Nachrichten vom Server wartet, und sobald eine
     * empfangen wurde, ein OnMessageReceived-Event abschickt
     */
    private MessageListener serverMessageListener;
    /**
     * Der {@link MessageListener.OnMessageReceivedListener}, der
     * darauf wartet, dass eine Nachricht erhalten wird.
     */
    private MessageListener.OnMessageReceivedListener receivedListener = new MessageListener.OnMessageReceivedListener() {
        @Override
        public void onMessageReceived(String name, String msg) {
            //          Die Nachricht wird einfach hinzugefgt, wobei die Nachricht immer empfangen
            //          (= Message.Type.Received) wurde
            addReceivedMessage(name, msg, Message.Type.Received);
        }

        @Override
        public void onServerMessageReceived(String msg) {
            addMessage(msg, Message.Type.Announcement);
        }
    };
    /**
     * Dieser ClosingDetector frgt ab, ob der MessageListener aufhren soll auf Nachrichten zu hren.
     */
    private MessageListener.ClosingDetector closingDetector = new MessageListener.ClosingDetector() {
        @Override
        public boolean isNotToBeClosed(Thread runningThr) {
            //          Da serverMessageListener in #onStop auf null gesetzt wird, und geschleifte Threads auf den ausfhrenden
            //          Thread abhngig gemacht werden soll, knnen wir Referenzen des ausfhrenden Threads und des Listeners vergleichen
            return serverMessageListener == runningThr && online;
        }
    };

    /**
     * Diese Variale zeigt an, ob das Handy derzeit online ist, oder nicht
     */
    private boolean online;
    /**
     * Diese Variable zeigt an, ob der Server erreicht werden konnte, oder nicht
     */
    private boolean serverReached;
    /**
     * Diese Variable zeigt an, ob wir gerade versuchen uns mit dem Server zu verbinden
     */
    private boolean tryingToConnect;

    /**
     * Dieses Callback soll uns so schnell wie mglich und automatisch wieder mit dem
     * Server verbinden.
     */
    private ConnectivityManager.NetworkCallback internetCallback = new ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            super.onAvailable(network);

            if (!online) {
                ColoredSnackbar.make(ContextCompat.getColor(MainActivity.this, R.color.colorConnectionRestored),
                        mMessagesView, getString(R.string.internetmessage_reconnect), Snackbar.LENGTH_SHORT,
                        ContextCompat.getColor(MainActivity.this, R.color.colorConnectionFont)).show();
                connectToServer();
            }
            online = true;
        }

        @Override
        public void onLost(Network network) {
            super.onLost(network);

            ColoredSnackbar.make(ContextCompat.getColor(MainActivity.this, R.color.colorConnectionLost),
                    mMessagesView, getString(R.string.internetmessage_disconnect), Snackbar.LENGTH_SHORT,
                    ContextCompat.getColor(MainActivity.this, R.color.colorConnectionFont)).show();
            online = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        //      Initialisierung der Nachrichtenliste
        mMessages = new ArrayList<>();
        //      TODO: Evtl. gespeicherte Nachrichten laden?

        //      Initialisierug der Einstellungen
        settingsBottomSheetLayout = (RelativeLayout) findViewById(R.id.settingsBottomSheet);
        settingsBottomSheetBehaviour = BottomSheetBehavior.from(settingsBottomSheetLayout);

        nameTextView = (EditText) findViewById(R.id.userNameEditText);

        SharedPreferences prefs = getSharedPreferences(getString(R.string.prefs_settings_preference), MODE_PRIVATE);
        if (prefs.getBoolean(getString(R.string.prefs_settings_firstLaunch), true)) {
            currentName = getString(R.string.defaultUserName);

            prefs.edit().putBoolean(getString(R.string.prefs_settings_firstLaunch), false).apply();
            settingsBottomSheetBehaviour.setState(BottomSheetBehavior.STATE_EXPANDED);
        } else {
            currentName = prefs.getString(getString(R.string.prefs_settings_currentname),
                    getString(R.string.defaultUserName));
            settingsBottomSheetBehaviour.setState(BottomSheetBehavior.STATE_HIDDEN);
        }

        nameTextView.setText(currentName);

        //      Intialisierung der UI
        mMessagesView = (RecyclerView) findViewById(R.id.messagesView);

        mLayoutManager = new LinearLayoutManager(this);
        mLayoutManager.setStackFromEnd(true);
        mMessagesView.setLayoutManager(mLayoutManager);

        mMessagesAdapter = new MessageAdapter(mMessages);
        mMessagesView.setAdapter(mMessagesAdapter);

        mSendBtn = (FloatingActionButton) findViewById(R.id.sendBtn);
        mSendBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage();
            }
        });

        mSendEditText = (EditText) findViewById(R.id.sendEditText);

        tryingToConnect = false;

        //      we'll check for internet, and if there is any, we'll start the connectivity
        //      if not we'll simply state that we're offline
        if (isConnectionAvailable()) {
            online = true;
            connectToServer();
        } else {
            online = false;
        }

        //      and we'll set up the NetworkCallback that is to reconnect us as soon as possible
        ((ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE))
                .registerNetworkCallback(new NetworkRequest.Builder().build(), internetCallback);

        //      Zum Schluss initialisieren wir die Funktion, mit der wir den FloatingActionButton dragbar machen :D
        buildFollowingFun();
    }

    /**
     * Diese Methode erstellt die Funktion, mit der man deL FloatingActionButton draggen
     * kann, woraufhin die CardView fr die Nachricht folgt
     */
    private void buildFollowingFun() {
        //      Frag einfach nicht, das ist Copy-Paste aus einem anderen
        //      Projekt von mir, und funktioniert einfach
        FloatingActionButton fab = mSendBtn;
        CardView cv = (CardView) findViewById(R.id.sendCardView);

        final SpringSystem springSystem = SpringSystem.create();

        final Spring fabSpringX = springSystem.createSpring();
        final Spring fabSpringY = springSystem.createSpring();

        new Actor.Builder(springSystem, fab).addMotion(fabSpringX, MotionProperty.X)
                .addMotion(fabSpringY, MotionProperty.Y).build();

        final Spring cvFollowerSpringX = springSystem.createSpring();
        final Spring cvFollowerSpringY = springSystem.createSpring();

        cvFollowerSpringX.addListener(new Performer(cv, View.TRANSLATION_X));
        cvFollowerSpringY.addListener(new Performer(cv, View.TRANSLATION_Y));

        final SpringImitator cvImitatorX = new SpringImitator(cvFollowerSpringX);
        final SpringImitator cvImitatorY = new SpringImitator(cvFollowerSpringY);

        fabSpringX.addListener(cvImitatorX);
        fabSpringY.addListener(cvImitatorY);
    }

    @Override
    protected void onResume() {
        super.onResume();

        //      we'll check for internet, and if there is any, we'll start the connectivity
        //      if not we'll simply state that we're offline
        if (isConnectionAvailable()) {
            online = true;
            connectToServer();
        } else {
            online = false;
        }

        //      and we'll set up the NetworkCallback that is to reconnect us as soon as possible
        ((ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE))
                .registerNetworkCallback(new NetworkRequest.Builder().build(), internetCallback);
    }

    /**
     * Diese Methode ermittelt, ob derzeit Internet verfgbar ist.
     *
     * @return ob Internet verfgbar ist
     */
    private boolean isConnectionAvailable() {
        ConnectivityManager manager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
        Network[] networks = manager.getAllNetworks();
        for (Network net : networks) {
            if (manager.getNetworkInfo(net).isAvailable()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Diese Methode baut eine Verbindung zum Server auf, von dem wir die Nachrichten bekommen
     */
    private void connectToServer() {
        if (!tryingToConnect && !serverReached && online) {
            tryingToConnect = true;

            //          Initialisierung der Serververbindung auf einem eigenen Thread, da Android
            //          keine Netzwerkkommunikation auf dem MainThread erlaubt
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        server = new Socket(getString(R.string.serverinfo_url),
                                getResources().getInteger(R.integer.serverinfo_port));

                        serverMessageSender = new MessageSender(new DataOutputStream(server.getOutputStream()));
                        serverMessageSender.setPingListener(pingListener);

                        serverMessageListener = new MessageListener(new DataInputStream(server.getInputStream()));
                        serverMessageListener.setClosingDetector(closingDetector);
                        serverMessageListener.setOnMessageReceivedListener(receivedListener);
                        serverMessageListener.bindMessageSender(serverMessageSender);
                        serverMessageListener.start();

                        serverMessageSender.changeName(currentName);

                        serverReached = true;
                    } catch (SocketException e) {
                        e.printStackTrace();
                        serverReached = false;
                        showDisconnectedErrorMessage();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    tryingToConnect = false;
                }
            }).start();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();

        if (online && serverReached) {
            try {
                //              Sobald die Activity endet lassen wir den MessageListener auslaufen, indem wir serverMessageListener auf null setzen,
                //              wodurch closingDetector#isNotToBeClosed(Thread) auf false gesetzt wird
                serverMessageListener = null;
                //              Und schlieen alle Streams
                server.close();
                serverMessageSender.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Diese Methode berprft, ob wir fr eine neue Nachricht des gegebenen Typs eine Datumsanzeige bentigen.
     * Eine Datumsanzeige ist bentigt, falls der Tag der letzten Nachricht, die keine Announcement-Nachricht ist nicht heute ist,
     * und der Typ der Nachricht selbst nicht Announcement ist.<br>
     * Falls die Datumsanzeige bentigt ist, wird diese von dieser Methode hinzugefgt.
     *
     * @param type Der Typ der hinzuzufgenden Nachricht
     */
    private void testForDateNeeded(Message.Type type) {
        //      falls wir kein Announcement anzeigen wollen
        if (type != Message.Type.Announcement) {
            //          und keine Nachricht da ist
            if (mMessages.size() == 0 || getLastMessage() == null) {
                //              fgen wir das heutige Datum definitiv als Announcement hinzu
                addMessage(new SimpleDateFormat(getString(R.string.dateannouncement_template))
                        .format(Calendar.getInstance().getTime()), Message.Type.Announcement);
            } else {
                //              falls eine Nachricht da ist, bekommen wir deren Zeit
                Calendar lastMessageTime = Calendar.getInstance();
                lastMessageTime.setTimeInMillis(getLastMessage().getTime());
                //              und die derzeitige Systemzeit
                Calendar currentTime = Calendar.getInstance();

                //              und falls diese nicht am selben Tag sind
                if (notSameDay(lastMessageTime, currentTime)) {
                    //                  fgen wir das heutige Datum als Announcement hinzu
                    addMessage(new SimpleDateFormat(getString(R.string.dateannouncement_template))
                            .format(Calendar.getInstance().getTime()), Message.Type.Announcement);
                }
            }
        }
    }

    /**
     * Diese Methode fgt eine gegebene Nachricht des gegebenen Typs zu der Liste hinzu, zeigt diese in der Liste an,
     * und lsst das RecyclerView zu dieser Nachricht scrollen.
     *
     * @param msg  Die Nachricht, die hinzugefgt werden soll
     * @param type Der Typ der Nachricht, die hinzugefgt werden soll
     */
    private void addMessage(final String msg, final Message.Type type) {
        testForDateNeeded(type);

        //      Da diese Methode von anderen Threads aufgerufen werden knnen, wir allerdings auf Views zugreifen
        //      mssen wir das Ganze mit der Methode runOnUiThread(Runnable) ausfhren
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //              wir fgen die Nachricht zur Liste hinzu
                mMessages.add(new Message(msg, type));
                //              zeigen es sie im RecyclerView an
                mMessagesAdapter.notifyItemInserted(mMessages.size() - 1);
                //              und scrollen zu der Nachricht
                mMessagesView.smoothScrollToPosition(mMessages.size() - 1);
            }
        });
    }

    /**
     * Diese Methode fgt eine empfangene Nachricht zu der Liste hinzu, zeigt diese in der Liste an,
     * und lsst das RecyclerView zu dieser Nachricht scrollen.
     *
     * @param name Der Name des Users von dem die Nachricht stammt
     * @param msg  Die Nachricht, die hinzugefgt werden soll
     * @param type Der Typ der Nachricht, die hinzugefgt werden soll
     */
    private void addReceivedMessage(final String name, final String msg, final Message.Type type) {
        testForDateNeeded(type);

        //      Da diese Methode von anderen Threads aufgerufen werden knnen, wir allerdings auf Views zugreifen
        //      mssen wir das Ganze mit der Methode runOnUiThread(Runnable) ausfhren
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //              wir fgen die Nachricht zur Liste hinzu
                mMessages.add(new ReceivedMessage(name, msg, type));
                //              zeigen es sie im RecyclerView an
                mMessagesAdapter.notifyItemInserted(mMessages.size() - 1);
                //              und scrollen zu der Nachricht
                mMessagesView.smoothScrollToPosition(mMessages.size() - 1);
            }
        });
    }

    /**
     * Diese Methode ermittelt, ob zwei Calendar-Objekte eine Zeit an verschiedenen Tagen reprsentieren, oder nicht.
     * Die Parameter sind beliebig vertauschbar; deren Reihenfolge hat keinen Einfluss auf den
     *
     * @param time1 Das erste Calendar-Objekt
     * @param time2 Das erste Calendar-Ojekt
     * @return Ob die Calendar-Ojekte verschiedene Tage reprsentieren, oder nicht
     */
    private boolean notSameDay(Calendar time1, Calendar time2) {
        return time1.get(Calendar.DAY_OF_MONTH) != time2.get(Calendar.DAY_OF_MONTH)
                || time1.get(Calendar.MONTH) != time2.get(Calendar.MONTH)
                || time1.get(Calendar.YEAR) != time2.get(Calendar.YEAR);
    }

    /**
     * Diese Methode gibt ihnen die letzte Nachricht, welche nicht vom Typ Announcement ist.
     * Falls keine solche Nachricht existiert wird {@code null} zurckgegeben.
     *
     * @return die letzte Nachricht, welche nicht vom Typ Announcement ist; {@code null}, falls keine existiert
     */
    @Nullable
    private Message getLastMessage() {
        //      wir fangen bei der letzten Nachricht an
        for (int i = mMessages.size() - 1; i >= 0; i--) {
            //          bei der ersten Nachricht, die nicht vom Typ Announcement ist
            if (mMessages.get(i).getMessageType() != Message.Type.Announcement) {
                //              geben wir diese zurck
                return mMessages.get(i);
            }
        }
        //      falls wir keine gefunden haben geben wir null zurck
        return null;
    }

    /**
     * Diese Klasse sendet die Nachricht, die momentan im TextFeld steht an den Server, und
     * fgt diese zum Chat hinzu
     */
    private void sendMessage() {
        //      Wir verhindern leere Nachrichten
        if (!mSendEditText.getText().toString().trim().equals("")) {
            if (online && serverReached) {
                //              bekommen die zu verschickende Nachricht
                String msg = mSendEditText.getText().toString();

                //              Senden sie an den Server
                serverMessageSender.send(msg);

                //              fgen sie zum Chat hinzu
                addMessage(msg, Message.Type.Sent);
                //              und lschen das TextFeld
                mSendEditText.setText("");
            } else if (online) {
                showDisconnectedErrorMessage();
            } else {
                showOfflineErrorMessage();
            }
        }
    }

    /**
     * Diese Methode leitet einen Ping des Servers ein
     */
    private void pingServer() {
        if (online && serverReached) {
            serverMessageSender.pingServer();
        } else if (online) {
            showDisconnectedErrorMessage();
        } else {
            showOfflineErrorMessage();
        }
    }

    /**
     * Diese Methode zeigt dem User, dass er gerade offline ist, woraus dieser ableiten
     * knnen sollte, dass er keine Nachrichten versenden kann.
     */
    private void showOfflineErrorMessage() {
        ColoredSnackbar.make(ContextCompat.getColor(this, R.color.colorConnectionLost), mMessagesView,
                getString(R.string.internetmessage_offline), Snackbar.LENGTH_SHORT,
                ContextCompat.getColor(this, R.color.colorConnectionFont)).show();
    }

    /**
     * Diese Methode zeigt dem User, dass er gerade keine Verbindung zu dem Server hat, woraus dieser ableiten
     * knnen sollte, dass er keine Nachrichten versenden kann.
     * Auerdem wird ihm die Mglichkeit geboten zu versuchen sich mit dem Server zu verbinden.
     */
    private void showDisconnectedErrorMessage() {
        ColoredSnackbar
                .make(ContextCompat.getColor(this, R.color.colorConnectionLost), mMessagesView,
                        getString(R.string.internetmessage_serverdown), Snackbar.LENGTH_SHORT,
                        ContextCompat.getColor(this, R.color.colorConnectionFont))
                .setAction(R.string.internetmessage_serverdown_reconnect, new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        connectToServer();
                    }
                }).show();
    }

    /**
     * Diese Methode zeigt dem User, dass sein eingegebener Name nicht gltig ist
     */
    private void showInvalidUserNameErrorMessage() {
        ColoredSnackbar.make(ContextCompat.getColor(this, R.color.colorConnectionLost), mMessagesView,
                getString(R.string.settingsmessage_invalidusername), Snackbar.LENGTH_SHORT,
                ContextCompat.getColor(this, R.color.colorConnectionFont)).show();
    }

    /**
     * Diese Methode speichert die Einstellungen, und lsst das Einstellungs-Panel verschwinden
     * @param v das View, das geklickt wurde
     */
    public void saveSettings(View v) {
        //      wir bekommen den neuen Namen
        String newName = nameTextView.getText().toString();

        //      verhindern den alten und alle leere Namen
        if (!newName.trim().equals(currentName) && !newName.trim().equals("")) {

            //          bernehmen den neuen Namen, und speichern ihn intern
            currentName = newName;
            getSharedPreferences(getString(R.string.prefs_settings_preference), MODE_PRIVATE).edit()
                    .putString(getString(R.string.prefs_settings_currentname), currentName).apply();

            if (online) {
                if (serverReached) {
                    //                  falls wir mit dem Server verbunden sind schicken wir den Namen an den Server;
                    //                  ansonsten geben wir die entsprechende Fehlermeldung aus
                    serverMessageSender.changeName(newName);
                } else {
                    showDisconnectedErrorMessage();
                }
            } else {
                showOfflineErrorMessage();
            }
        } else {
            showInvalidUserNameErrorMessage();
        }

        //      und lassen das Einstellungs-Panel verschwinden
        settingsBottomSheetBehaviour.setState(BottomSheetBehavior.STATE_HIDDEN);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        //      Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        //      Handle action bar item clicks here. The action bar will
        //      automatically handle clicks on the Home/Up button, so long
        //      as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //      noinspection SimplifiableIfStatement
        if (id == R.id.action_ping) {
            pingServer();
            return true;
        } else if (id == R.id.action_settings) {
            settingsBottomSheetBehaviour.setState(BottomSheetBehavior.STATE_EXPANDED);
        }

        return super.onOptionsItemSelected(item);
    }
}