diff src/com/five_ten_sg/connectbot/ConsoleActivity.java @ 0:0ce5cc452d02

initial version
author Carl Byington <carl@five-ten-sg.com>
date Thu, 22 May 2014 10:41:19 -0700
parents
children e0f5e706a655
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/five_ten_sg/connectbot/ConsoleActivity.java	Thu May 22 10:41:19 2014 -0700
@@ -0,0 +1,1508 @@
+/*
+ * 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(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(PreferenceConstants.DEFAULT_FONT_SIZE_WIDTH, "80"));
+                ((EditText) resizeView.findViewById(R.id.height))
+                .setText(prefs.getString(PreferenceConstants.DEFAULT_FONT_SIZE_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);
+                        }
+                        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(sessionOpen);
+        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);
+        }
+    }
+}