org.woltage.irssiconnectbot.ConsoleActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.woltage.irssiconnectbot.ConsoleActivity.java

Source

/*
 * ConnectBot: simple, powerful, open-source SSH client for Android
 * Copyright 2007 Kenny Root, Jeffrey Sharkey
 *
 * 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 org.woltage.irssiconnectbot;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;

import org.woltage.irssiconnectbot.bean.PubkeyBean;
import org.woltage.irssiconnectbot.bean.SelectionArea;
import org.woltage.irssiconnectbot.service.PromptHelper;
import org.woltage.irssiconnectbot.service.TerminalBridge;
import org.woltage.irssiconnectbot.service.TerminalKeyListener;
import org.woltage.irssiconnectbot.service.TerminalManager;
import org.woltage.irssiconnectbot.util.InstallMosh;
import org.woltage.irssiconnectbot.util.PreferenceConstants;
import org.woltage.irssiconnectbot.util.PubkeyDatabase;
import org.woltage.irssiconnectbot.util.PubkeyUtils;

import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.view.MenuItemCompat;
import android.text.ClipboardManager;
import android.text.InputType;
import android.text.method.PasswordTransformationMethod;
import android.text.method.SingleLineTransformationMethod;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;

import com.bugsense.trace.BugSenseHandler;

import de.mud.terminal.vt320;

public class ConsoleActivity extends Activity {
    public final static String TAG = "ConnectBot.ConsoleActivity";

    protected static final int REQUEST_EDIT = 1;
    protected static final int REQUEST_SELECT = 2;

    protected static final int CLICK_TIME = 400;
    protected static final float MAX_CLICK_DISTANCE = 25f;
    protected static final int KEYBOARD_DISPLAY_TIME = 1500;

    // Direction to shift the ViewFlipper
    protected static final int SHIFT_LEFT = 0;
    protected static final int SHIFT_RIGHT = 1;

    protected ViewFlipper flip = null;
    protected TerminalManager bound = null;
    protected LayoutInflater inflater = null;

    protected SharedPreferences prefs = null;

    // determines whether or not menuitem accelerators are bound
    // otherwise they collide with an external keyboard's CTRL-char
    private boolean hardKeyboard = false;

    protected Uri requested;

    protected ClipboardManager clipboard;
    private RelativeLayout stringPromptGroup;
    protected EditText stringPrompt;
    private TextView stringPromptInstructions;

    private RelativeLayout booleanPromptGroup;
    private TextView booleanPrompt;
    private Button booleanYes, booleanNo;

    private TextView empty;

    private Animation slide_left_in, slide_left_out, slide_right_in, slide_right_out, fade_stay_hidden,
            fade_out_delayed;

    protected Animation keyboard_fade_in, keyboard_fade_out;

    private InputMethodManager inputManager;

    private MenuItem disconnect, copy, paste, portForward, resize, urlscan;

    protected TerminalBridge copySource = null;

    private boolean forcedOrientation;

    private ImageView mKeyboardButton;
    private ImageView mInputButton;

    private ServiceConnection connection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            bound = ((TerminalManager.TerminalBinder) service).getService();

            // let manager know about our event handling services
            bound.disconnectHandler = disconnectHandler;

            Log.d(TAG,
                    String.format("Connected to TerminalManager and found bridges.size=%d", bound.bridges.size()));

            bound.setResizeAllowed(true);

            // clear all animation flags, so that after onPause() and onResume() (i.e. switching using home key) no slide animation is shown
            flip.setAnimation(null);
            flip.setInAnimation(null);
            flip.setOutAnimation(null);

            // clear out any existing bridges and record requested index
            flip.removeAllViews();

            final String requestedNickname = (requested != null) ? requested.getFragment()
                    : (bound.defaultBridge != null) ? bound.defaultBridge.host.getNickname() : null;
            int requestedIndex = 0;

            TerminalBridge requestedBridge = bound.getConnectedBridge(requestedNickname);

            // If we didn't find the requested connection, try opening it
            if (requestedNickname != null && requestedBridge == null) {
                try {
                    Log.d(TAG, String.format(
                            "We couldnt find an existing bridge with URI=%s (nickname=%s), so creating one now",
                            requested.toString(), requestedNickname));
                    requestedBridge = bound.openConnection(requested);
                } catch (Exception e) {
                    Log.e(TAG, "Problem while trying to create new requested bridge from URI", e);
                }
            }

            // create views for all bridges on this service
            for (TerminalBridge bridge : bound.bridges) {

                final int currentIndex = addNewTerminalView(bridge);

                // check to see if this bridge was requested
                if (bridge == requestedBridge)
                    requestedIndex = currentIndex;
            }

            setDisplayedTerminal(requestedIndex);
        }

        public void onServiceDisconnected(ComponentName className) {
            // tell each bridge to forget about our prompt handler
            synchronized (bound.bridges) {
                for (TerminalBridge bridge : bound.bridges)
                    bridge.promptHelper.setHandler(null);
            }

            flip.removeAllViews();
            updateEmptyVisible();
            bound = null;
        }
    };

    protected Handler promptHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // someone below us requested to display a prompt
            updatePromptVisible();
        }
    };

    protected Handler disconnectHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "Someone sending HANDLE_DISCONNECT to parentHandler");

            // someone below us requested to display a password dialog
            // they are sending nickname and requested
            TerminalBridge bridge = (TerminalBridge) msg.obj;

            if (bridge.isAwaitingClose())
                closeBridge(bridge);
        }
    };

    /**
     * @param bridge
     */
    private void closeBridge(final TerminalBridge bridge) {
        synchronized (flip) {
            final int flipIndex = getFlipIndex(bridge);

            if (flipIndex >= 0) {
                if (flip.getDisplayedChild() == flipIndex) {
                    shiftCurrentTerminal(SHIFT_LEFT);
                }
                flip.removeViewAt(flipIndex);

                /* TODO Remove this workaround when ViewFlipper is fixed to
                 * listen to view removals. Android Issue 1784
                 */
                final int numChildren = flip.getChildCount();
                if (flip.getDisplayedChild() >= numChildren && numChildren > 0) {
                    flip.setDisplayedChild(numChildren - 1);
                }

                updateEmptyVisible();
            }

            // If we just closed the last bridge, go back to the previous activity.
            if (flip.getChildCount() == 0) {
                finish();
            }
        }
    }

    protected View findCurrentView(int id) {
        View view = flip.getCurrentView();
        if (view == null)
            return null;
        return view.findViewById(id);
    }

    protected PromptHelper getCurrentPromptHelper() {
        View view = findCurrentView(R.id.console_flip);
        if (!(view instanceof TerminalView))
            return null;
        return ((TerminalView) view).bridge.promptHelper;
    }

    protected void hideAllPrompts() {
        stringPromptGroup.setVisibility(View.GONE);
        booleanPromptGroup.setVisibility(View.GONE);
        // adjust screen size if it was changed during prompt input
        View view = findCurrentView(R.id.console_flip);
        if (!(view instanceof TerminalView))
            return;
        ((TerminalView) view).bridge.parentChanged((TerminalView) view);
    }

    // more like configureLaxMode -- enable network IO on UI thread
    private void configureStrictMode() {
        try {
            Class.forName("android.os.StrictMode");
            StrictModeSetup.run();
        } catch (ClassNotFoundException e) {
        }
    }

    @Override
    @TargetApi(11)
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        if (!InstallMosh.isInstallStarted()) {
            new InstallMosh(this);
        }

        configureStrictMode();
        hardKeyboard = getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY;

        hardKeyboard = hardKeyboard && !Build.MODEL.contains("Transformer");

        this.setContentView(R.layout.act_console);
        BugSenseHandler.setup(this, "d27a12dc");

        clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
        prefs = PreferenceManager.getDefaultSharedPreferences(this);

        // hide action bar if requested by user
        try {
            ActionBar actionBar = getActionBar();
            if (!prefs.getBoolean(PreferenceConstants.ACTIONBAR, true)) {
                actionBar.hide();
            }
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
        } catch (NoSuchMethodError error) {
            Log.w(TAG, "Android sdk version pre 11. Not touching ActionBar.");
        }

        // hide status bar if requested by user
        if (prefs.getBoolean(PreferenceConstants.FULLSCREEN, false)) {
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
        }

        // TODO find proper way to disable volume key beep if it exists.
        setVolumeControlStream(AudioManager.STREAM_MUSIC);

        // handle requested console from incoming intent
        requested = getIntent().getData();

        inflater = LayoutInflater.from(this);

        flip = (ViewFlipper) findViewById(R.id.console_flip);
        empty = (TextView) findViewById(android.R.id.empty);

        stringPromptGroup = (RelativeLayout) findViewById(R.id.console_password_group);
        stringPromptInstructions = (TextView) findViewById(R.id.console_password_instructions);
        stringPrompt = (EditText) findViewById(R.id.console_password);
        stringPrompt.setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (event.getAction() == KeyEvent.ACTION_UP)
                    return false;
                if (keyCode != KeyEvent.KEYCODE_ENTER)
                    return false;

                // pass collected password down to current terminal
                String value = stringPrompt.getText().toString();

                PromptHelper helper = getCurrentPromptHelper();
                if (helper == null)
                    return false;
                helper.setResponse(value);

                // finally clear password for next user
                stringPrompt.setText("");
                updatePromptVisible();

                return true;
            }
        });

        booleanPromptGroup = (RelativeLayout) findViewById(R.id.console_boolean_group);
        booleanPrompt = (TextView) findViewById(R.id.console_prompt);

        booleanYes = (Button) findViewById(R.id.console_prompt_yes);
        booleanYes.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                PromptHelper helper = getCurrentPromptHelper();
                if (helper == null)
                    return;
                helper.setResponse(Boolean.TRUE);
                updatePromptVisible();
            }
        });

        booleanNo = (Button) findViewById(R.id.console_prompt_no);
        booleanNo.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                PromptHelper helper = getCurrentPromptHelper();
                if (helper == null)
                    return;
                helper.setResponse(Boolean.FALSE);
                updatePromptVisible();
            }
        });

        // preload animations for terminal switching
        slide_left_in = AnimationUtils.loadAnimation(this, R.anim.slide_left_in);
        slide_left_out = AnimationUtils.loadAnimation(this, R.anim.slide_left_out);
        slide_right_in = AnimationUtils.loadAnimation(this, R.anim.slide_right_in);
        slide_right_out = AnimationUtils.loadAnimation(this, R.anim.slide_right_out);

        fade_out_delayed = AnimationUtils.loadAnimation(this, R.anim.fade_out_delayed);
        fade_stay_hidden = AnimationUtils.loadAnimation(this, R.anim.fade_stay_hidden);

        // Preload animation for keyboard button
        keyboard_fade_in = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_in);
        keyboard_fade_out = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_out);

        inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

        final RelativeLayout keyboardGroup = (RelativeLayout) findViewById(R.id.keyboard_group);

        if (Build.MODEL.contains("Transformer")
                && getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY
                && prefs.getBoolean(PreferenceConstants.ACTIONBAR, true)) {
            keyboardGroup.setEnabled(false);
            keyboardGroup.setVisibility(View.INVISIBLE);
        }

        mKeyboardButton = (ImageView) findViewById(R.id.button_keyboard);
        mKeyboardButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return;

                inputManager.showSoftInput(flip, InputMethodManager.SHOW_FORCED);
                keyboardGroup.setVisibility(View.GONE);
            }
        });

        final ImageView symButton = (ImageView) findViewById(R.id.button_sym);
        symButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return;

                TerminalView terminal = (TerminalView) flip;

                TerminalKeyListener handler = terminal.bridge.getKeyHandler();
                handler.showCharPickerDialog(terminal);
                keyboardGroup.setVisibility(View.GONE);
            }
        });

        mInputButton = (ImageView) findViewById(R.id.button_input);
        mInputButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return;

                final TerminalView terminal = (TerminalView) flip;
                Thread promptThread = new Thread(new Runnable() {
                    public void run() {
                        String inj = getCurrentPromptHelper().requestStringPrompt(null, "");
                        terminal.bridge.injectString(inj);
                    }
                });
                promptThread.setName("Prompt");
                promptThread.setDaemon(true);
                promptThread.start();

                keyboardGroup.setVisibility(View.GONE);
            }
        });

        final ImageView ctrlButton = (ImageView) findViewById(R.id.button_ctrl);
        ctrlButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return;
                TerminalView terminal = (TerminalView) flip;

                TerminalKeyListener handler = terminal.bridge.getKeyHandler();
                handler.metaPress(TerminalKeyListener.META_CTRL_ON);
                terminal.bridge.tryKeyVibrate();

                keyboardGroup.setVisibility(View.GONE);
            }
        });

        final ImageView escButton = (ImageView) findViewById(R.id.button_esc);
        escButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return;
                TerminalView terminal = (TerminalView) flip;

                TerminalKeyListener handler = terminal.bridge.getKeyHandler();
                handler.sendEscape();
                terminal.bridge.tryKeyVibrate();
                keyboardGroup.setVisibility(View.GONE);
            }
        });

        // detect fling gestures to switch between terminals
        final GestureDetector detect = new GestureDetector(new ICBSimpleOnGestureListener(this));

        flip.setLongClickable(true);
        flip.setOnTouchListener(new ICBOnTouchListener(this, keyboardGroup, detect));

    }

    /**
     *
     */
    private void configureOrientation() {
        String rotateDefault;
        if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS)
            rotateDefault = PreferenceConstants.ROTATION_PORTRAIT;
        else
            rotateDefault = PreferenceConstants.ROTATION_LANDSCAPE;

        String rotate = prefs.getString(PreferenceConstants.ROTATION, rotateDefault);
        if (PreferenceConstants.ROTATION_DEFAULT.equals(rotate))
            rotate = rotateDefault;

        // request a forced orientation if requested by user
        if (PreferenceConstants.ROTATION_LANDSCAPE.equals(rotate)) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            forcedOrientation = true;
        } else if (PreferenceConstants.ROTATION_PORTRAIT.equals(rotate)) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            forcedOrientation = true;
        } else if (PreferenceConstants.ROTATION_SENSOR.equals(rotate)) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
            forcedOrientation = false;
        } else {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
            forcedOrientation = false;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        View view = findCurrentView(R.id.console_flip);
        final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
        final boolean activeTerminal = (view instanceof TerminalView);
        boolean sessionOpen = false;
        boolean disconnected = false;
        boolean canForwardPorts = false;

        if (activeTerminal) {
            TerminalBridge bridge = ((TerminalView) view).bridge;
            sessionOpen = bridge.isSessionOpen();
            disconnected = bridge.isDisconnected();
            canForwardPorts = bridge.canFowardPorts();
        }

        menu.setQwertyMode(true);

        disconnect = menu.add(R.string.list_host_disconnect);
        if (hardKeyboard)
            disconnect.setAlphabeticShortcut('w');
        if (!sessionOpen && disconnected)
            disconnect.setTitle(R.string.console_menu_close);
        disconnect.setEnabled(activeTerminal);
        disconnect.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
        disconnect.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                // disconnect or close the currently visible session
                TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
                TerminalBridge bridge = terminalView.bridge;

                bridge.dispatchDisconnect(true);

                if (bound != null && prefs.getBoolean("unloadKeysOnDisconnect", true)) {
                    bound.lockUnusedKeys();
                }
                return true;
            }
        });

        copy = menu.add(R.string.console_menu_copy);
        if (hardKeyboard)
            copy.setAlphabeticShortcut('c');
        copy.setIcon(android.R.drawable.ic_menu_set_as);
        copy.setEnabled(activeTerminal);
        copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                // mark as copying and reset any previous bounds
                TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
                copySource = terminalView.bridge;

                SelectionArea area = copySource.getSelectionArea();
                area.reset();
                area.setBounds(copySource.buffer.getColumns(), copySource.buffer.getRows());
                area.setRow(copySource.buffer.getCursorRow());
                area.setColumn(copySource.buffer.getCursorColumn());

                copySource.setSelectingForCopy(true);

                // Make sure we show the initial selection
                copySource.redraw();

                Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_start), Toast.LENGTH_LONG)
                        .show();
                return true;
            }
        });

        paste = menu.add(R.string.console_menu_paste);
        if (hardKeyboard)
            paste.setAlphabeticShortcut('v');
        paste.setIcon(android.R.drawable.ic_menu_edit);
        paste.setEnabled(clipboard.hasText() && sessionOpen);
        paste.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                // force insert of clipboard text into current console
                TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
                TerminalBridge bridge = terminalView.bridge;

                // pull string from clipboard and generate all events to force
                // down
                String clip = clipboard.getText().toString();
                bridge.injectString(clip);

                return true;
            }
        });

        portForward = menu.add(R.string.console_menu_portforwards);
        if (hardKeyboard)
            portForward.setAlphabeticShortcut('f');
        portForward.setIcon(android.R.drawable.ic_menu_manage);
        portForward.setEnabled(sessionOpen && canForwardPorts);
        portForward.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
                TerminalBridge bridge = terminalView.bridge;

                Intent intent = new Intent(ConsoleActivity.this, PortForwardListActivity.class);
                intent.putExtra(Intent.EXTRA_TITLE, bridge.host.getId());
                ConsoleActivity.this.startActivityForResult(intent, REQUEST_EDIT);
                return true;
            }
        });

        urlscan = menu.add(R.string.console_menu_urlscan);
        if (hardKeyboard)
            urlscan.setAlphabeticShortcut('u');
        urlscan.setIcon(android.R.drawable.ic_menu_search);
        urlscan.setEnabled(activeTerminal);
        urlscan.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                final TerminalView terminal = (TerminalView) findCurrentView(R.id.console_flip);

                TerminalKeyListener handler = terminal.bridge.getKeyHandler();
                handler.urlScan(terminal);

                return true;
            }
        });

        resize = menu.add(R.string.console_menu_resize);
        if (hardKeyboard)
            resize.setAlphabeticShortcut('s');
        resize.setIcon(android.R.drawable.ic_menu_crop);
        resize.setEnabled(sessionOpen);
        resize.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);

                final View resizeView = inflater.inflate(R.layout.dia_resize, null, false);
                ((EditText) resizeView.findViewById(R.id.width))
                        .setText(prefs.getString("default_fsize_width", "80"));
                ((EditText) resizeView.findViewById(R.id.height))
                        .setText(prefs.getString("default_fsize_height", "25"));

                new AlertDialog.Builder(ConsoleActivity.this).setView(resizeView)
                        .setPositiveButton(R.string.button_resize, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                int width, height;
                                try {
                                    width = Integer.parseInt(
                                            ((EditText) resizeView.findViewById(R.id.width)).getText().toString());
                                    height = Integer.parseInt(
                                            ((EditText) resizeView.findViewById(R.id.height)).getText().toString());
                                } catch (NumberFormatException nfe) {
                                    // TODO change this to a real dialog where we can
                                    // make the input boxes turn red to indicate an error.
                                    return;
                                }
                                if (width > 0 && height > 0) {
                                    terminalView.forceSize(width, height);
                                    terminalView.bridge.parentChanged(terminalView);
                                } else {
                                    new AlertDialog.Builder(ConsoleActivity.this)
                                            .setMessage("Width and height must be higher than zero.")
                                            .setTitle("Error").show();
                                }
                            }
                        }).setNegativeButton(android.R.string.cancel, null).create().show();

                return true;
            }
        });

        MenuItem ctrlKey = menu.add("Ctrl");
        ctrlKey.setEnabled(activeTerminal);
        ctrlKey.setIcon(R.drawable.button_ctrl);
        MenuItemCompat.setShowAsAction(ctrlKey, MenuItem.SHOW_AS_ACTION_IF_ROOM);
        ctrlKey.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return false;

                TerminalView terminal = (TerminalView) flip;

                TerminalKeyListener handler = terminal.bridge.getKeyHandler();
                handler.metaPress(TerminalKeyListener.META_CTRL_ON);
                terminal.bridge.tryKeyVibrate();
                return true;
            }
        });

        MenuItem escKey = menu.add("Esc");
        escKey.setEnabled(activeTerminal);
        escKey.setIcon(R.drawable.button_esc);
        MenuItemCompat.setShowAsAction(escKey, MenuItem.SHOW_AS_ACTION_IF_ROOM);
        escKey.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return false;

                TerminalView terminal = (TerminalView) flip;

                TerminalKeyListener handler = terminal.bridge.getKeyHandler();
                handler.sendEscape();
                terminal.bridge.tryKeyVibrate();
                return true;
            }
        });

        MenuItem symKey = menu.add("SYM");
        symKey.setEnabled(activeTerminal);
        symKey.setIcon(R.drawable.button_sym);
        MenuItemCompat.setShowAsAction(symKey, MenuItem.SHOW_AS_ACTION_IF_ROOM);
        symKey.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return false;

                TerminalView terminal = (TerminalView) flip;

                TerminalKeyListener handler = terminal.bridge.getKeyHandler();
                handler.showCharPickerDialog(terminal);
                return true;
            }
        });

        MenuItem inputButton = menu.add("Input");
        inputButton.setEnabled(activeTerminal);
        inputButton.setIcon(R.drawable.button_input);
        MenuItemCompat.setShowAsAction(inputButton, MenuItem.SHOW_AS_ACTION_IF_ROOM);
        inputButton.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return false;

                final TerminalView terminal = (TerminalView) flip;
                Thread promptThread = new Thread(new Runnable() {
                    public void run() {
                        String inj = getCurrentPromptHelper().requestStringPrompt(null, "");
                        terminal.bridge.injectString(inj);
                    }
                });
                promptThread.setName("Prompt");
                promptThread.setDaemon(true);
                promptThread.start();
                return true;
            }
        });

        MenuItem keyboard = menu.add("Show Keyboard");
        keyboard.setEnabled(activeTerminal);
        keyboard.setIcon(R.drawable.button_keyboard);
        MenuItemCompat.setShowAsAction(keyboard, MenuItem.SHOW_AS_ACTION_IF_ROOM);
        keyboard.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                View flip = findCurrentView(R.id.console_flip);
                if (flip == null)
                    return false;

                inputManager.showSoftInput(flip, InputMethodManager.SHOW_FORCED);
                return true;
            }
        });

        MenuItem keys = menu.add(R.string.console_menu_pubkeys);
        keys.setIcon(android.R.drawable.ic_lock_lock);
        keys.setIntent(new Intent(this, PubkeyListActivity.class));
        keys.setEnabled(activeTerminal);
        keys.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                Intent intent = new Intent(ConsoleActivity.this, PubkeyListActivity.class);
                intent.putExtra(PubkeyListActivity.PICK_MODE, true);
                ConsoleActivity.this.startActivityForResult(intent, REQUEST_SELECT);
                return true;
            }
        });

        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        setVolumeControlStream(AudioManager.STREAM_NOTIFICATION);

        final View view = findCurrentView(R.id.console_flip);
        boolean activeTerminal = (view instanceof TerminalView);
        boolean sessionOpen = false;
        boolean disconnected = false;
        boolean canForwardPorts = false;

        if (activeTerminal) {
            TerminalBridge bridge = ((TerminalView) view).bridge;
            sessionOpen = bridge.isSessionOpen();
            disconnected = bridge.isDisconnected();
            canForwardPorts = bridge.canFowardPorts();
        }

        disconnect.setEnabled(activeTerminal);
        if (sessionOpen || !disconnected)
            disconnect.setTitle(R.string.list_host_disconnect);
        else
            disconnect.setTitle(R.string.console_menu_close);
        copy.setEnabled(activeTerminal);
        paste.setEnabled(clipboard.hasText() && sessionOpen);
        portForward.setEnabled(sessionOpen && canForwardPorts);
        urlscan.setEnabled(activeTerminal);
        resize.setEnabled(sessionOpen);

        return true;
    }

    @Override
    public void onOptionsMenuClosed(Menu menu) {
        super.onOptionsMenuClosed(menu);

        setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home: // someone hit the home icon on the actionbar
            Intent intent = new Intent(this, HostListActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onStart() {
        super.onStart();

        // connect with manager service to find all bridges
        // when connected it will insert all views
        bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "onPause called");

        if (forcedOrientation && bound != null)
            bound.setResizeAllowed(false);

        // save the current flip view for later
        updateDefault();
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume called");

        // Make sure we don't let the screen fall asleep.
        // This also keeps the Wi-Fi chipset from disconnecting us.
        if (prefs.getBoolean(PreferenceConstants.KEEP_ALIVE, true)) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        } else {
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }

        configureOrientation();

        if (forcedOrientation && bound != null)
            bound.setResizeAllowed(true);
    }

    /*
     * (non-Javadoc)
     *
     * @see android.app.Activity#onNewIntent(android.content.Intent)
     */
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        Log.d(TAG, "onNewIntent called");

        requested = intent.getData();

        if (requested == null) {
            Log.e(TAG, "Got null intent data in onNewIntent()");
            return;
        }

        if (bound == null) {
            Log.e(TAG, "We're not bound in onNewIntent()");
            return;
        }

        TerminalBridge requestedBridge = bound.getConnectedBridge(requested.getFragment());
        int requestedIndex = 0;

        synchronized (flip) {
            if (requestedBridge == null) {
                // If we didn't find the requested connection, try opening it

                try {
                    Log.d(TAG, String.format(
                            "We couldnt find an existing bridge with URI=%s (nickname=%s), so creating one now",
                            requested.toString(), requested.getFragment()));
                    requestedBridge = bound.openConnection(requested);
                } catch (Exception e) {
                    Log.e(TAG, "Problem while trying to create new requested bridge from URI", e);
                    // TODO: We should display an error dialog here.
                    return;
                }

                requestedIndex = addNewTerminalView(requestedBridge);
            } else {
                final int flipIndex = getFlipIndex(requestedBridge);
                if (flipIndex > requestedIndex) {
                    requestedIndex = flipIndex;
                }
            }

            setDisplayedTerminal(requestedIndex);
        }
    }

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

        unbindService(connection);
    }

    protected void shiftCurrentTerminalOrIrssiWindow(final int direction, boolean upperScreenHalf) {
        if (upperScreenHalf || prefs.getString("swipe", "").equals("default")) {
            shiftCurrentTerminal(direction);
        } else {
            shiftIrssiWindow(direction);
        }
    }

    protected void shiftCurrentTerminal(final int direction) {
        View overlay;
        synchronized (flip) {
            boolean shouldAnimate = flip.getChildCount() > 1;

            // Only show animation if there is something else to go to.
            if (shouldAnimate) {
                // keep current overlay from popping up again
                overlay = findCurrentView(R.id.terminal_overlay);
                if (overlay != null)
                    overlay.startAnimation(fade_stay_hidden);

                if (direction == SHIFT_LEFT) {
                    flip.setInAnimation(slide_left_in);
                    flip.setOutAnimation(slide_left_out);
                    flip.showNext();
                } else if (direction == SHIFT_RIGHT) {
                    flip.setInAnimation(slide_right_in);
                    flip.setOutAnimation(slide_right_out);
                    flip.showPrevious();
                }
            }

            ConsoleActivity.this.updateDefault();

            if (shouldAnimate) {
                // show overlay on new slide and start fade
                overlay = findCurrentView(R.id.terminal_overlay);
                if (overlay != null)
                    overlay.startAnimation(fade_out_delayed);
            }

            updatePromptVisible();
        }
    }

    protected void shiftIrssiWindow(final int direction) {
        int keyCode = 0;
        View flip = findCurrentView(R.id.console_flip);
        if (flip == null)
            return;
        TerminalView terminal = (TerminalView) flip;

        if (direction == SHIFT_LEFT) {
            if (prefs.getString("swipe", "").equals("channel_swipe_inverted")) {
                keyCode = vt320.KEY_RIGHT;
            } else {
                keyCode = vt320.KEY_LEFT;
            }
        } else if (direction == SHIFT_RIGHT) {
            if (prefs.getString("swipe", "").equals("channel_swipe_inverted")) {
                keyCode = vt320.KEY_LEFT;
            } else {
                keyCode = vt320.KEY_RIGHT;
            }
        }

        if (keyCode == vt320.KEY_RIGHT) {
            ((vt320) terminal.bridge.buffer).write(0x0E);
        } else if (keyCode == vt320.KEY_LEFT) {
            ((vt320) terminal.bridge.buffer).write(0x10);
        }
        terminal.bridge.tryKeyVibrate();
        terminal.bridge.tryScrollVibrate();
    }

    /**
     * Save the currently shown {@link TerminalView} as the default. This is
     * saved back down into {@link TerminalManager} where we can read it again
     * later.
     */
    private void updateDefault() {
        // update the current default terminal
        View view = findCurrentView(R.id.console_flip);
        if (!(view instanceof TerminalView))
            return;

        TerminalView terminal = (TerminalView) view;
        if (bound == null)
            return;
        bound.defaultBridge = terminal.bridge;

        // any previous intent extra data is irrelevant now, thus set requested to null and save current defaultBridge
        requested = null;
    }

    protected void updateEmptyVisible() {
        // update visibility of empty status message
        empty.setVisibility((flip.getChildCount() == 0) ? View.VISIBLE : View.GONE);
    }

    /**
     * Show any prompts requested by the currently visible {@link TerminalView}.
     */
    protected void updatePromptVisible() {
        // check if our currently-visible terminalbridge is requesting any
        // prompt services
        View view = findCurrentView(R.id.console_flip);

        // Hide all the prompts in case a prompt request was canceled
        hideAllPrompts();

        if (!(view instanceof TerminalView)) {
            // we dont have an active view, so hide any prompts
            return;
        }

        PromptHelper prompt = ((TerminalView) view).bridge.promptHelper;
        if (String.class.equals(prompt.promptRequested)) {
            stringPromptGroup.setVisibility(View.VISIBLE);

            String instructions = prompt.promptInstructions;
            boolean password = prompt.passwordRequested;
            if (instructions != null && instructions.length() > 0) {
                stringPromptInstructions.setVisibility(View.VISIBLE);
                stringPromptInstructions.setText(instructions);
            } else
                stringPromptInstructions.setVisibility(View.GONE);

            if (password) {
                stringPrompt.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
                stringPrompt.setTransformationMethod(PasswordTransformationMethod.getInstance());
            } else {
                stringPrompt.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
                stringPrompt.setTransformationMethod(SingleLineTransformationMethod.getInstance());
            }

            stringPrompt.setText("");
            stringPrompt.setHint(prompt.promptHint);
            stringPrompt.requestFocus();

        } else if (Boolean.class.equals(prompt.promptRequested)) {
            booleanPromptGroup.setVisibility(View.VISIBLE);
            booleanPrompt.setText(prompt.promptHint);
            booleanYes.requestFocus();

        } else {
            hideAllPrompts();
            view.requestFocus();
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        Log.d(TAG, String.format("onConfigurationChanged; requestedOrientation=%d, newConfig.orientation=%d",
                getRequestedOrientation(), newConfig.orientation));
        if (bound != null) {
            if (forcedOrientation
                    && (newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE
                            && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
                    || (newConfig.orientation != Configuration.ORIENTATION_PORTRAIT
                            && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT))
                bound.setResizeAllowed(false);
            else
                bound.setResizeAllowed(true);

            bound.hardKeyboardHidden = (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES);

            mKeyboardButton.setVisibility(bound.hardKeyboardHidden ? View.VISIBLE : View.GONE);
            mInputButton.setVisibility(bound.hardKeyboardHidden ? View.VISIBLE : View.GONE);
        }
    }

    /**
     * Adds a new TerminalBridge to the current set of views in our ViewFlipper.
     *
     * @param bridge
     *            TerminalBridge to add to our ViewFlipper
     * @return the child index of the new view in the ViewFlipper
     */
    private int addNewTerminalView(TerminalBridge bridge) {
        // let them know about our prompt handler services
        bridge.promptHelper.setHandler(promptHandler);

        // inflate each terminal view
        RelativeLayout view = (RelativeLayout) inflater.inflate(R.layout.item_terminal, flip, false);

        // set the terminal overlay text
        TextView overlay = (TextView) view.findViewById(R.id.terminal_overlay);
        overlay.setText(bridge.host.getNickname());

        // and add our terminal view control, using index to place behind
        // overlay
        TerminalView terminal = new TerminalView(ConsoleActivity.this, bridge);
        terminal.setId(R.id.console_flip);
        view.addView(terminal, 0);

        synchronized (flip) {
            // finally attach to the flipper
            flip.addView(view);
            return flip.getChildCount() - 1;
        }
    }

    private int getFlipIndex(TerminalBridge bridge) {
        synchronized (flip) {
            final int children = flip.getChildCount();
            for (int i = 0; i < children; i++) {
                final View view = flip.getChildAt(i).findViewById(R.id.console_flip);

                if (view == null || !(view instanceof TerminalView)) {
                    // How did that happen?
                    continue;
                }

                final TerminalView tv = (TerminalView) view;

                if (tv.bridge == bridge) {
                    return i;
                }
            }
        }

        return -1;
    }

    /**
     * Displays the child in the ViewFlipper at the requestedIndex and updates
     * the prompts.
     *
     * @param requestedIndex
     *            the index of the terminal view to display
     */
    private void setDisplayedTerminal(int requestedIndex) {
        synchronized (flip) {
            try {
                // show the requested bridge if found, also fade out overlay
                flip.setDisplayedChild(requestedIndex);
                flip.getCurrentView().findViewById(R.id.terminal_overlay).startAnimation(fade_out_delayed);
            } catch (NullPointerException npe) {
                Log.d(TAG, "View went away when we were about to display it", npe);
            }

            updatePromptVisible();
            updateEmptyVisible();
        }
    }

    /*
    * (non-Javadoc)
    *
    * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
    */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case REQUEST_SELECT:
            if (resultCode == Activity.RESULT_OK) {
                long pubkeyId = data.getLongExtra(PubkeyListActivity.PICKED_PUBKEY_ID, -1);
                PubkeyDatabase pubkeyDatabase = new PubkeyDatabase(this);
                PubkeyBean pubkey = pubkeyDatabase.findPubkeyById(pubkeyId);
                setupPublicKey(pubkey);
            }
            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
            break;

        }
    }

    /**
     * @param pubkey
     */
    private void setupPublicKey(PubkeyBean pubkey) {
        Log.d(TAG, "setupPublicKey, pubKey=" + pubkey.getNickname());

        try {
            String openSSHPubkey;
            if (PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType())) {
                openSSHPubkey = PubkeyUtils.convertToOpenSSHFormat(new String(pubkey.getPrivateKey()),
                        pubkey.getNickname());
            } else {
                PublicKey pk = pubkey.getPublicKey();
                openSSHPubkey = PubkeyUtils.convertToOpenSSHFormat(pk, pubkey.getNickname());
            }

            final TerminalView terminal = (TerminalView) findCurrentView(R.id.console_flip);
            terminal.bridge
                    .injectString("mkdir ~/.ssh -pm 700 ; echo " + openSSHPubkey + " >> ~/.ssh/authorized_keys");
        } catch (InvalidKeyException e) {
            Log.e(TAG, e.getMessage(), e);
        } catch (IOException e) {
            Log.e(TAG, e.getMessage(), e);
        }

    }
}