package com.facebook.stetho.sample; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.Nullable; public class IRCChatActivity extends Activity { private static final int DEFAULT_PORT = 6667; private static final String EXTRA_HOST_AND_MAYBE_PORT = "host"; private static final String EXTRA_NICKNAME = "nickname"; private SimpleIRCConnectionManager mSimpleIRCConnectionManager; private ExecutorService mConnectionExecutor; private boolean mIsTearingDown; private ListView mConsoleDisplay; private IRCConsoleRowAdapter mConsoleRowAdapter; private TextView mConsoleInput; public static void showForResult( Activity context, int requestCode, String hostAndMaybePort, String nickname) { Intent intent = new Intent(context, IRCChatActivity.class); intent.putExtra(EXTRA_HOST_AND_MAYBE_PORT, hostAndMaybePort); intent.putExtra(EXTRA_NICKNAME, nickname); context.startActivityForResult(intent, requestCode); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.irc_chat_activity); mConsoleDisplay = (ListView) findViewById(R.id.console_display); mConsoleRowAdapter = new IRCConsoleRowAdapter(this); mConsoleDisplay.setAdapter(mConsoleRowAdapter); mConsoleInput = (TextView) findViewById(R.id.console_input); mConsoleInput.setOnEditorActionListener(mOnConsoleInputEditorAction); findViewById(R.id.console_send).setOnClickListener(mConsoleSendClicked); // Will re-enable once we connect... mConsoleInput.setEnabled(false); mSimpleIRCConnectionManager = new SimpleIRCConnectionManager( getIntent().getStringExtra(EXTRA_HOST_AND_MAYBE_PORT), getIntent().getStringExtra(EXTRA_NICKNAME)); mConnectionExecutor = Executors.newCachedThreadPool(); mConnectionExecutor.execute(new Runnable() { @Override public void run() { mSimpleIRCConnectionManager.runConnectLoop(); } }); } @Override protected void onDestroy() { mSimpleIRCConnectionManager.shutdown(); mConnectionExecutor.shutdown(); mIsTearingDown = true; super.onDestroy(); } private void onConnected() { mConsoleInput.setEnabled(true); } private void onIncomingMessage(String message) { if (mIsTearingDown) { return; } mConsoleRowAdapter.add(message); } private void onDisconnectOrConnectFailed(@Nullable IOException exception) { if (mIsTearingDown) { return; } mIsTearingDown = true; final String error; if (exception != null) { Toast.makeText( this, "Error: " + exception.getMessage(), Toast.LENGTH_LONG) .show(); error = exception.getMessage(); } else { error = null; } new IRCChatActivityResult(error).setResult(this); finish(); } private final TextView.OnEditorActionListener mOnConsoleInputEditorAction = new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { switch (actionId) { case EditorInfo.IME_ACTION_SEND: doSendMessage(); return true; default: return false; } } }; private final View.OnClickListener mConsoleSendClicked = new View.OnClickListener() { @Override public void onClick(View v) { doSendMessage(); } }; private void doSendMessage() { final String message = mConsoleInput.getText().toString(); mConsoleInput.setText(""); if (!TextUtils.isEmpty(message)) { mConnectionExecutor.execute(new Runnable() { @Override public void run() { mSimpleIRCConnectionManager.send(message); } }); } } private class SimpleIRCConnectionManager { @Nullable private volatile IRCClientConnection mConnection; private volatile boolean mShutdownRequested; private final String mHost; private final int mPort; private final String mNickname; public SimpleIRCConnectionManager(String hostAndPort, String nickname) { String[] hostAndPortParts = hostAndPort.split(":", 2); if (hostAndPortParts.length == 2) { mHost = hostAndPortParts[0]; mPort = Integer.parseInt(hostAndPortParts[1]); } else { mHost = hostAndPort; mPort = DEFAULT_PORT; } mNickname = nickname; } public void runConnectLoop() { boolean graceful = false; try { final IRCClientConnection conn = IRCClientConnection.connect(mHost, mPort); try { mConnection = conn; doConnectLoop(conn); } finally { mConnection = null; conn.close(); } graceful = true; } catch (IOException e) { invokeOnDisconnectOrConnectFailed(e); } finally { if (graceful) { invokeOnDisconnectOrConnectFailed(null /* exception */); } } } private void doConnectLoop(IRCClientConnection conn) throws IOException { if (mShutdownRequested) { return; } runOnUiThread(new Runnable() { @Override public void run() { onConnected(); } }); conn.send(String.format(Locale.US, "NICK %s", mNickname)); conn.send(String.format(Locale.US, "USER %s %s blablabla :%s", mNickname, mHost, mNickname)); while (!mShutdownRequested) { final String message = conn.read(); if (message == null) { break; } runOnUiThread(new Runnable() { @Override public void run() { onIncomingMessage(message); } }); } } public void send(String message) { IRCClientConnection conn = mConnection; if (conn != null) { try { conn.send(message); } catch (IOException e) { invokeOnDisconnectOrConnectFailed(e); } } } public void shutdown() { mShutdownRequested = true; // Force a socket closure to cause an immediate effect in Stetho. IRCClientConnection conn = mConnection; if (conn != null) { try { conn.close(); } catch (IOException e) { invokeOnDisconnectOrConnectFailed(e); } } } private void invokeOnDisconnectOrConnectFailed(@Nullable final IOException e) { runOnUiThread(new Runnable() { @Override public void run() { onDisconnectOrConnectFailed(e); } }); } } private static class IRCConsoleRowAdapter extends ArrayAdapter<String> { public IRCConsoleRowAdapter(Context context) { super(context, R.layout.irc_console_row); } } public static class IRCChatActivityResult { private static final String EXTRA_RESULT_CONNECT_ERROR = "error"; @Nullable public final String connectError; public static IRCChatActivityResult fromResult(int resultCode, Intent data) { if (resultCode == RESULT_CANCELED) { return new IRCChatActivityResult(null /* connectError */); } else { return new IRCChatActivityResult(data.getStringExtra(EXTRA_RESULT_CONNECT_ERROR)); } } public IRCChatActivityResult(@Nullable String connectError) { this.connectError = connectError; } public boolean wasUserDisconnect() { return connectError == null; } private void setResult(Activity activity) { if (wasUserDisconnect()) { activity.setResult(RESULT_CANCELED); } else { activity.setResult( RESULT_OK, new Intent(activity, IRCChatActivity.class) .putExtra(EXTRA_RESULT_CONNECT_ERROR, connectError)); } } } }