view src/com/five_ten_sg/connectbot/ConsoleActivity.java @ 308:42b15aaa7ac7 ganymed

merge
author Carl Byington <carl@five-ten-sg.com>
date Wed, 30 Jul 2014 14:21:50 -0700
parents cf81ae79931d
children 885f2bca6032
line wrap: on
line 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 com.five_ten_sg.connectbot;

import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import com.five_ten_sg.connectbot.bean.SelectionArea;
import com.five_ten_sg.connectbot.service.PromptHelper;
import com.five_ten_sg.connectbot.service.TerminalBridge;
import com.five_ten_sg.connectbot.service.TerminalKeyListener;
import com.five_ten_sg.connectbot.service.TerminalManager;
import com.five_ten_sg.connectbot.util.FileChooser;
import com.five_ten_sg.connectbot.util.FileChooserCallback;
import com.five_ten_sg.connectbot.util.PreferenceConstants;
import com.five_ten_sg.connectbot.util.TransferThread;
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.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.text.ClipboardManager;
import android.text.InputType;
import android.text.method.PasswordTransformationMethod;
import android.text.method.SingleLineTransformationMethod;
import android.util.FloatMath;
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.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
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 de.mud.terminal.vt320;

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

    protected static final int REQUEST_EDIT = 1;

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

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

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

    private 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;

    // determines whether we are in the fullscreen mode
    private static final int FULLSCREEN_ON = 1;
    private static final int FULLSCREEN_OFF = 2;

    private int fullScreen;

    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 RelativeLayout keyboardGroup;
    private Runnable keyboardGroupHider;

    private TextView empty;

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

    private Animation keyboard_fade_in, keyboard_fade_out;
    private float lastX, lastY;

    private InputMethodManager inputManager;

    private MenuItem disconnect, copy, paste, portForward, resize, urlscan, screenCapture, download, upload;

    protected TerminalBridge copySource = null;
    private int lastTouchRow, lastTouchCol;

    private boolean forcedOrientation;

    private Handler handler = new Handler();

    private ImageView mKeyboardButton;

    private ActionBarWrapper actionBar;
    private boolean inActionBarMenu = false;

    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);
            bound.hardKeyboardHidden = (getResources().getConfiguration().hardKeyboardHidden ==
                                        Configuration.HARDKEYBOARDHIDDEN_YES);

            // set fullscreen value
            if (bound.getFullScreen() == 0) {
                setFullScreen(FULLSCREEN_OFF);
            }
            else if (fullScreen != bound.getFullScreen())
                setFullScreen(bound.getFullScreen());

            // clear out any existing bridges and record requested index
            flip.removeAllViews();
            final String requestedNickname = (requested != null) ? requested.getFragment() : null;
            int requestedIndex = -1;
            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;
                    // store this bridge as default bridge
                    bound.defaultBridge = bridge;
                }
            }

            // if no bridge was requested, try using default bridge
            if (requestedIndex < 0) {
                requestedIndex = getFlipIndex(bound.defaultBridge);

                if (requestedIndex < 0)
                    requestedIndex = 0;
            }

            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 window back if size was changed during prompt input
        View view = findCurrentView(R.id.console_flip);

        if (!(view instanceof TerminalView)) return;

        ((TerminalView)view).bridge.parentChanged((TerminalView)view);
    }

    private void showEmulatedKeys() {
        keyboardGroup.startAnimation(keyboard_fade_in);
        keyboardGroup.setVisibility(View.VISIBLE);
        actionBar.show();

        if (keyboardGroupHider != null)
            handler.removeCallbacks(keyboardGroupHider);

        keyboardGroupHider = new Runnable() {
            public void run() {
                if (keyboardGroup.getVisibility() == View.GONE || inActionBarMenu)
                    return;

                keyboardGroup.startAnimation(keyboard_fade_out);
                keyboardGroup.setVisibility(View.GONE);
                actionBar.hide();
                keyboardGroupHider = null;
            }
        };
        handler.postDelayed(keyboardGroupHider, KEYBOARD_DISPLAY_TIME);
    }

    private void hideEmulatedKeys() {
        if (keyboardGroupHider != null)
            handler.removeCallbacks(keyboardGroupHider);

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

    // more like configureLaxMode -- enable network IO on UI thread
    private void configureStrictMode() {
        try {
            Class.forName("android.os.StrictMode");
            StrictModeSetup.run();
        }
        catch (ClassNotFoundException e) {
        }
    }
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        configureStrictMode();
        hardKeyboard = getResources().getConfiguration().keyboard ==
                       Configuration.KEYBOARD_QWERTY;
        this.setContentView(R.layout.act_console);
        clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
        prefs = PreferenceManager.getDefaultSharedPreferences(this);
        // 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);
        keyboardGroup     = (RelativeLayout) findViewById(R.id.keyboard_group);
        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);
                hideEmulatedKeys();
            }
        });
        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;
                terminal.bridge.showCharPickerDialog();
                keyboardGroup.setVisibility(View.GONE);
            }
        });
        symButton.setOnLongClickListener(new OnLongClickListener() {
            public boolean onLongClick(View view) {
                View flip = findCurrentView(R.id.console_flip);

                if (flip == null) return false;

                TerminalView terminal = (TerminalView)flip;
                terminal.bridge.showArrowsDialog();
                return true;
            }
        });
        final ImageView 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);
                hideEmulatedKeys();
            }
        });
        ctrlButton.setOnLongClickListener(new OnLongClickListener() {
            public boolean onLongClick(View view) {
                View flip = findCurrentView(R.id.console_flip);

                if (flip == null) return false;

                TerminalView terminal = (TerminalView)flip;
                terminal.bridge.showCtrlDialog();
                return true;
            }
        });
        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();
                hideEmulatedKeys();
            }
        });
        escButton.setOnLongClickListener(new OnLongClickListener() {
            public boolean onLongClick(View view) {
                View flip = findCurrentView(R.id.console_flip);

                if (flip == null) return false;

                TerminalView terminal = (TerminalView)flip;
                terminal.bridge.showFKeysDialog();
                return true;
            }
        });
        actionBar = ActionBarWrapper.getActionBar(this);
        actionBar.setDisplayHomeAsUpEnabled(true);
        actionBar.hide();
        actionBar.addOnMenuVisibilityListener(new ActionBarWrapper.OnMenuVisibilityListener() {
            public void onMenuVisibilityChanged(boolean isVisible) {
                inActionBarMenu = isVisible;

                if (isVisible == false) {
                    hideEmulatedKeys();
                }
            }
        });
        // detect fling gestures to switch between terminals
        final GestureDetector gestDetect = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
            private float totalY = 0;
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                final float distx = e2.getRawX() - e1.getRawX();
                final float disty = e2.getRawY() - e1.getRawY();
                final int goalwidth = flip.getWidth() / 2;

                // need to slide across half of display to trigger console change
                // make sure user kept a steady hand horizontally
                if (Math.abs(disty) < (flip.getHeight() / 4)) {
                    if (distx > goalwidth) {
                        shiftCurrentTerminal(SHIFT_RIGHT);
                        return true;
                    }

                    if (distx < -goalwidth) {
                        shiftCurrentTerminal(SHIFT_LEFT);
                        return true;
                    }
                }

                return false;
            }
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                // if copying, then ignore
                if (copySource != null && copySource.isSelectingForCopy())
                    return false;

                if (e1 == null || e2 == null)
                    return false;

                // if releasing then reset total scroll
                if (e2.getAction() == MotionEvent.ACTION_UP) {
                    totalY = 0;
                }

                // activate consider if within x tolerance
                if (Math.abs(e1.getX() - e2.getX()) < ViewConfiguration.getTouchSlop() * 4) {
                    View flip = findCurrentView(R.id.console_flip);

                    if (flip == null) return false;

                    TerminalView terminal = (TerminalView)flip;
                    // estimate how many rows we have scrolled through
                    // accumulate distance that doesn't trigger immediate scroll
                    totalY += distanceY;
                    final int moved = (int)(totalY / terminal.bridge.charHeight);

                    // consume as scrollback only if towards right half of screen
                    if (e2.getX() > flip.getWidth() / 2) {
                        if (moved != 0) {
                            int base = terminal.bridge.buffer.getWindowBase();
                            terminal.bridge.buffer.setWindowBase(base + moved);
                            totalY = 0;
                            return true;
                        }
                    }
                    else {
                        // otherwise consume as pgup/pgdown for every 5 lines
                        if (moved > 5) {
                            ((vt320)terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0);
                            terminal.bridge.tryKeyVibrate();
                            totalY = 0;
                            return true;
                        }
                        else if (moved < -5) {
                            ((vt320)terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0);
                            terminal.bridge.tryKeyVibrate();
                            totalY = 0;
                            return true;
                        }
                    }
                }

                return false;
            }
            /*
             * Enables longpress and popups menu
             *
             * @see
             * android.view.GestureDetector.SimpleOnGestureListener#
             * onLongPress(android.view.MotionEvent)
             *
             * @return void
             */
            @Override
            public void onLongPress(MotionEvent e) {
                List<String> itemList = new ArrayList<String>();
                final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);

                if (terminalView == null) return;

                final TerminalBridge bridge = terminalView.bridge;

                if (fullScreen == FULLSCREEN_ON)
                    itemList.add(ConsoleActivity.this
                                 .getResources().getString(R.string.longpress_disable_full_screen_mode));
                else
                    itemList.add(ConsoleActivity.this
                                 .getResources().getString(R.string.longpress_enable_full_screen_mode));

                itemList.add(ConsoleActivity.this
                             .getResources().getString(R.string.longpress_change_font_size));

                if (prefs.getBoolean(PreferenceConstants.EXTENDED_LONGPRESS, false)) {
                    itemList.add(ConsoleActivity.this
                                 .getResources().getString(R.string.longpress_arrows_dialog));
                    itemList.add(ConsoleActivity.this
                                 .getResources().getString(R.string.longpress_fkeys_dialog));
                    itemList.add(ConsoleActivity.this
                                 .getResources().getString(R.string.longpress_ctrl_dialog));
                    itemList.add(ConsoleActivity.this
                                 .getResources().getString(R.string.longpress_sym_dialog));
                }

                if (itemList.size() > 0) {
                    AlertDialog.Builder builder = new AlertDialog.Builder(ConsoleActivity.this);
                    builder.setTitle(R.string.longpress_select_action);
                    builder.setItems(itemList.toArray(new CharSequence[itemList.size()]),
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int item) {
                            switch (item) {
                                case 0:
                                    if (fullScreen == FULLSCREEN_ON) {
                                        setFullScreen(FULLSCREEN_OFF);
                                    }
                                    else
                                        setFullScreen(FULLSCREEN_ON);

                                    break;

                                case 1:
                                    bridge.showFontSizeDialog();
                                    break;

                                case 2:
                                    bridge.showArrowsDialog();
                                    break;

                                case 3:
                                    bridge.showFKeysDialog();
                                    break;

                                case 4:
                                    bridge.showCtrlDialog();
                                    break;

                                case 5:
                                    bridge.showCharPickerDialog();
                            }
                        }
                    });
                    AlertDialog alert = builder.create();
                    alert.show();
                }
            }
        });
        flip.setLongClickable(true);
        flip.setOnTouchListener(new OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                // when copying, highlight the area
                if (copySource != null && copySource.isSelectingForCopy()) {
                    int row = (int)FloatMath.floor(event.getY() / copySource.charHeight);
                    int col = (int)FloatMath.floor(event.getX() / copySource.charWidth);
                    SelectionArea area = copySource.getSelectionArea();

                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:

                            // recording starting area
                            if (area.isSelectingOrigin()) {
                                area.setRow(row);
                                area.setColumn(col);
                                lastTouchRow = row;
                                lastTouchCol = col;
                                copySource.redraw();
                            }

                            return true;

                        case MotionEvent.ACTION_MOVE:

                            /* ignore when user hasn't moved since last time so
                             * we can fine-tune with directional pad
                             */
                            if (row == lastTouchRow && col == lastTouchCol)
                                return true;

                            // if the user moves, start the selection for other corner
                            area.finishSelectingOrigin();
                            // update selected area
                            area.setRow(row);
                            area.setColumn(col);
                            lastTouchRow = row;
                            lastTouchCol = col;
                            copySource.redraw();
                            return true;

                        case MotionEvent.ACTION_UP:

                            /* If they didn't move their finger, maybe they meant to
                             * select the rest of the text with the directional pad.
                             */
                            if (area.getLeft() == area.getRight() &&
                                    area.getTop() == area.getBottom()) {
                                return true;
                            }

                            // copy selected area to clipboard
                            String copiedText = area.copyFrom(copySource.buffer);
                            clipboard.setText(copiedText);
                            Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_done, copiedText.length()), Toast.LENGTH_LONG).show();

                        // fall through to clear state
                        case MotionEvent.ACTION_CANCEL:
                            // make sure we clear any highlighted area
                            area.reset();
                            copySource.setSelectingForCopy(false);
                            copySource.redraw();
                            return true;
                    }
                }

                Configuration config = getResources().getConfiguration();

                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    lastX = event.getX();
                    lastY = event.getY();
                }
                else if (event.getAction() == MotionEvent.ACTION_UP
                         && keyboardGroup.getVisibility() == View.GONE
                         && event.getEventTime() - event.getDownTime() < CLICK_TIME
                         && Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE
                         && Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) {
                    showEmulatedKeys();
                }

                // pass any touch events back to detector
                TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
                return  terminalView.mScaleDetector.onTouchEvent(event) | gestDetect.onTouchEvent(event);
            }
        });
    }

    /**
     *
     */
    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 {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
            forcedOrientation = false;
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        View view = findCurrentView(R.id.console_flip);
        final boolean activeTerminal = (view instanceof TerminalView);
        boolean sessionOpen = false;
        boolean disconnected = false;
        boolean canForwardPorts = false;
        boolean canTransferFiles = false;

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

        menu.setQwertyMode(true);

        if (!PreferenceConstants.PRE_HONEYCOMB) {
            MenuItem ctrlKey = menu.add(getString(R.string.fullscreen));
            ctrlKey.setEnabled(activeTerminal);
            ctrlKey.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
            ctrlKey.setOnMenuItemClickListener(new OnMenuItemClickListener() {
                public boolean onMenuItemClick(MenuItem menuItem) {
                    if (fullScreen == FULLSCREEN_ON) {
                        setFullScreen(FULLSCREEN_OFF);
                    }
                    else
                        setFullScreen(FULLSCREEN_ON);

                    return 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);
                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());
                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;
            }
        });
        resize = menu.add(R.string.console_menu_resize);

        if (hardKeyboard)
            resize.setAlphabeticShortcut('r');

        resize.setIcon(android.R.drawable.ic_menu_crop);
        resize.setEnabled(activeTerminal);
        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("80");
                ((EditText) resizeView.findViewById(R.id.height)).setText("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);
                        }
                        else {
                            new AlertDialog.Builder(ConsoleActivity.this)
                            .setTitle(R.string.resize_error_title)
                            .setMessage(R.string.resize_error_width_height)
                            .setNegativeButton(R.string.button_close, null)
                            .show();
                        }
                    }
                }).setNeutralButton(R.string.button_resize_reset, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        terminalView.bridge.resetSize(terminalView);
                    }
                }).setNegativeButton(android.R.string.cancel, null)
                .create().show();
                return true;
            }
        });
        screenCapture = menu.add(R.string.console_menu_screencapture);

        if (hardKeyboard)
            screenCapture.setAlphabeticShortcut('s');

        screenCapture.setIcon(android.R.drawable.ic_menu_camera);
        screenCapture.setEnabled(activeTerminal);
        screenCapture.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
                terminalView.bridge.captureScreen();
                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('l');

        urlscan.setIcon(android.R.drawable.ic_menu_search);
        urlscan.setEnabled(activeTerminal);
        urlscan.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                View flip = findCurrentView(R.id.console_flip);

                if (flip == null) return true;

                TerminalView terminal = (TerminalView)flip;
                TerminalKeyListener handler = terminal.bridge.getKeyHandler();
                handler.urlScan(terminal);
                return true;
            }
        });
        download = menu.add(R.string.console_menu_download);
        download.setAlphabeticShortcut('d');
        download.setEnabled(sessionOpen && canTransferFiles);
        download.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                final String downloadFolder = prefs.getString(PreferenceConstants.DOWNLOAD_FOLDER, "");
                final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
                final TerminalBridge bridge = terminalView.bridge;
                final EditText textField = new EditText(ConsoleActivity.this);
                new AlertDialog.Builder(ConsoleActivity.this)
                .setTitle(R.string.transfer_select_remote_download_title)
                .setMessage(R.string.transfer_select_remote_download_desc)
                .setView(textField)
                .setPositiveButton(R.string.transfer_button_download, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        TransferThread transfer = new TransferThread(ConsoleActivity.this, handler);

                        if (!prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true))
                            transfer.setProgressDialogMessage(getString(R.string.transfer_downloading));

                        transfer.download(bridge, textField.getText().toString(), null, downloadFolder);
                    }
                }).setNegativeButton(android.R.string.cancel, null).create().show();
                return true;
            }
        });
        upload = menu.add(R.string.console_menu_upload);
        upload.setAlphabeticShortcut('u');
        upload.setEnabled(sessionOpen && canTransferFiles);
        upload.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
                FileChooser.selectFile(ConsoleActivity.this, ConsoleActivity.this,
                                       FileChooser.REQUEST_CODE_SELECT_FILE,
                                       getString(R.string.file_chooser_select_file, getString(R.string.select_for_upload)));
                return true;
            }
        });
        return true;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);

        switch (requestCode) {
            case FileChooser.REQUEST_CODE_SELECT_FILE:
                if (resultCode == RESULT_OK && intent != null) {
                    Uri uri = intent.getData();

                    try {
                        if (uri != null) {
                            fileSelected(new File(URI.create(uri.toString())));
                        }
                        else {
                            String filename = intent.getDataString();

                            if (filename != null) {
                                fileSelected(new File(URI.create(filename)));
                            }
                        }
                    }
                    catch (IllegalArgumentException e) {
                        Log.e(TAG, "Couldn't read from selected file", e);
                    }
                }

                break;
        }
    }

    public void fileSelected(final File f) {
        String destFileName;
        String uploadFolder = prefs.getString(PreferenceConstants.REMOTE_UPLOAD_FOLDER, null);
        final TransferThread transfer = new TransferThread(ConsoleActivity.this, handler);
        Log.d(TAG, "File chooser returned " + f);

        if (uploadFolder == null)
            uploadFolder = "";

        if (!uploadFolder.equals("") && uploadFolder.charAt(uploadFolder.length() - 1) != '/')
            destFileName = uploadFolder + "/" + f.getName();
        else
            destFileName = uploadFolder + f.getName();

        if (prefs.getBoolean(PreferenceConstants.UPLOAD_DESTINATION_PROMPT, true)) {
            final EditText fileDest = new EditText(ConsoleActivity.this);
            fileDest.setSingleLine();
            fileDest.setText(destFileName);
            new AlertDialog.Builder(ConsoleActivity.this)
            .setTitle(R.string.transfer_select_remote_upload_dest_title)
            .setMessage(getResources().getString(R.string.transfer_select_remote_upload_dest_desc) + "\n" + f.getPath())
            .setView(fileDest)
            .setPositiveButton(R.string.transfer_button_upload, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    if (!prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true))
                        transfer.setProgressDialogMessage(getString(R.string.transfer_uploading));

                    TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
                    TerminalBridge bridge = terminalView.bridge;
                    File uf = new File(fileDest.getText().toString());
                    String name = "", parent = "";

                    if (uf.getParent() != null)
                        parent = uf.getParent().toString();

                    if (uf.getName() != null)
                        name = uf.getName().toString();

                    transfer.upload(bridge, f.toString(), name, parent);
                }
            }).setNegativeButton(android.R.string.cancel, null).create().show();
        }
        else {
            if (!prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true))
                transfer.setProgressDialogMessage(getString(R.string.transfer_uploading));

            TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
            TerminalBridge bridge = terminalView.bridge;
            transfer.upload(bridge, f.toString(), null, uploadFolder);
        }
    }

    @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;
        boolean canTransferFiles = false;

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

        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(activeTerminal);
        download.setEnabled(sessionOpen && canTransferFiles);
        upload.setEnabled(sessionOpen && canTransferFiles);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                Intent intent = new Intent(this, HostListActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(intent);
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onOptionsMenuClosed(Menu menu) {
        super.onOptionsMenuClosed(menu);
        setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }

    @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);

        if (getResources().getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
            this.mKeyboardButton.setVisibility(View.GONE);
        }
    }

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

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

    @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 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();
                }
            }

            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();
        }
    }

    /**
     * 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) bound.defaultBridge = terminal.bridge;

        // tell the bridge monitor it has the topmost visible window now.
        if (terminal.bridge.monitor != null) terminal.bridge.monitor.activate();
    }

    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);
        }
    }

    /**
     * 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();
        }
    }

    private void setFullScreen(int fullScreen) {
        if (fullScreen != this.fullScreen) {
            if (fullScreen == FULLSCREEN_ON) {
                getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                                     WindowManager.LayoutParams.FLAG_FULLSCREEN);
            }
            else {
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            }

            this.fullScreen = fullScreen;

            if (bound != null)
                bound.setFullScreen(this.fullScreen);
        }
    }
}