org.connectbot.ConsoleFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.connectbot.ConsoleFragment.java

Source

/*
 * ConnectBot: simple, powerful, open-source SSH client for Android
 * Copyright 2007 Kenny Root, Jeffrey Sharkey
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.connectbot;

import java.lang.ref.WeakReference;
import java.util.List;

import org.connectbot.bean.HostBean;
import org.connectbot.bean.SelectionArea;
import org.connectbot.service.PromptHelper;
import org.connectbot.service.TerminalBridge;
import org.connectbot.service.TerminalKeyListener;
import org.connectbot.service.TerminalManager;
import org.connectbot.util.PreferenceConstants;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.text.ClipboardManager;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;
import de.mud.terminal.vt320;

public class ConsoleFragment extends Fragment {
    public final static String TAG = "ConnectBot.ConsoleFragment";

    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 LayoutInflater inflater = null;

    private SharedPreferences prefs = null;

    protected Uri requested;

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

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

    private TextView empty;

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

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

    private InputMethodManager inputManager;

    private MenuItem mMenuDisconnect;
    private MenuItem mMenuCopy;
    private MenuItem mMenuPaste;
    private MenuItem mMenuPortForwards;
    private MenuItem mMenuResize;
    private MenuItem mMenuUrlScan;

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

    private boolean forcedOrientation;

    private Handler mUIHandler = new Handler();

    private ImageView mKeyboardButton;

    private ConsoleFragmentContainer mListener;

    public interface ConsoleFragmentContainer {
        public TerminalManager getTerminalManager();

        public void onTerminalViewChanged(HostBean host);
    }

    /**
     * Create a new instance of ConsoleFragment
     */
    static ConsoleFragment newInstance() {
        final ConsoleFragment f = new ConsoleFragment();

        // Supply num input as an argument.
        /*
         * Bundle args = new Bundle(); args.putInt("num", num);
         * f.setArguments(args);
         */

        return f;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (ConsoleFragmentContainer) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement ConsoleFragmentContainer");
        }
    }

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

    public void setupConsoles() {
        final TerminalManager bound = mListener.getTerminalManager();

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

        if (requestedIndex >= 0) {
            setDisplayedTerminal(requestedIndex);
        }
    }

    public void destroyConsoles() {
        flip.removeAllViews();
        updateEmptyVisible();
    }

    /**
     * Removes the view associated with a {@code TerminalBridge} animating if
     * necessary. This should only be called from the UI thread.
     *
     * @param bridge {@code TerminalBridge} which to remove views.
     */
    public void removeBridgeView(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);
                }
            }

            // If we just closed the last bridge, go back to the previous activity.
            if (flip.getChildCount() == 0) {
                mListener.onTerminalViewChanged(null);
                flip.setVisibility(View.GONE);
                hideAllPrompts();
                updateEmptyVisible();
            }
        }
    }

    protected View findCurrentView(int id) {
        if (flip == null)
            return null;

        final View view = flip.getCurrentView();

        if (view == null)
            return null;

        return view.findViewById(id);
    }

    protected PromptHelper getCurrentPromptHelper() {
        final 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);
    }

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

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

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

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

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

        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View v = inflater.inflate(R.layout.frg_console, container, false);

        this.inflater = inflater;

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

        stringPromptGroup = (RelativeLayout) v.findViewById(R.id.console_password_group);
        stringPromptInstructions = (TextView) v.findViewById(R.id.console_password_instructions);
        stringPrompt = (EditText) v.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) v.findViewById(R.id.console_boolean_group);
        booleanPrompt = (TextView) v.findViewById(R.id.console_prompt);

        booleanYes = (Button) v.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) v.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(getActivity(), R.anim.slide_left_in);
        slide_left_out = AnimationUtils.loadAnimation(getActivity(), R.anim.slide_left_out);
        slide_right_in = AnimationUtils.loadAnimation(getActivity(), R.anim.slide_right_in);
        slide_right_out = AnimationUtils.loadAnimation(getActivity(), R.anim.slide_right_out);

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

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

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

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

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

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

        final ImageView ctrlButton = (ImageView) v.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);

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

        final ImageView escButton = (ImageView) v.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();

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

        // detect fling gestures to switch between terminals
        final GestureDetector detect = 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;
            }

        });

        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) Math.floor(event.getY() / copySource.charHeight);
                    int col = (int) Math.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(getActivity(), 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) {
                    keyboardGroup.startAnimation(keyboard_fade_in);
                    keyboardGroup.setVisibility(View.VISIBLE);

                    mUIHandler.postDelayed(new Runnable() {
                        public void run() {
                            if (keyboardGroup.getVisibility() == View.GONE)
                                return;

                            keyboardGroup.startAnimation(keyboard_fade_out);
                            keyboardGroup.setVisibility(View.GONE);
                        }
                    }, KEYBOARD_DISPLAY_TIME);
                }

                // pass any touch events back to detector
                return detect.onTouchEvent(event);
            }

        });

        return v;
    }

    /**
     *
     */
    // TODO: Move to activity
    /*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 void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
        super.onCreateOptionsMenu(menu, menuInflater);
        menuInflater.inflate(R.menu.host_menu, menu);
        menu.setQwertyMode(true);

        mMenuDisconnect = menu.findItem(R.id.disconnect);
        mMenuCopy = menu.findItem(R.id.copy);
        mMenuPaste = menu.findItem(R.id.paste);
        mMenuPortForwards = menu.findItem(R.id.port_forwards);
        mMenuUrlScan = menu.findItem(R.id.url_scan);
        mMenuResize = menu.findItem(R.id.resize);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.disconnect: {
            // disconnect or close the currently visible session
            final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
            final TerminalBridge bridge = terminalView.bridge;

            bridge.dispatchDisconnect(true);
            return true;
        }
        case R.id.copy: {
            // mark as copying and reset any previous bounds
            final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
            copySource = terminalView.bridge;

            final 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(getActivity(), getString(R.string.console_copy_start), Toast.LENGTH_LONG).show();
            return true;
        }
        case R.id.paste: {
            // force insert of clipboard text into current console
            final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
            final TerminalBridge bridge = terminalView.bridge;

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

            return true;
        }
        case R.id.port_forwards: {
            final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
            final TerminalBridge bridge = terminalView.bridge;

            final Intent intent = new Intent(getActivity(), PortForwardListActivity.class);
            intent.putExtra(Intent.EXTRA_TITLE, bridge.host.getId());

            startActivityForResult(intent, REQUEST_EDIT);

            return true;
        }
        case R.id.url_scan: {
            final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);

            final List<String> urls = terminalView.bridge.scanForURLs();

            final Dialog urlDialog = new Dialog(getActivity());
            urlDialog.setTitle(R.string.console_menu_urlscan);

            ListView urlListView = new ListView(getActivity());

            final URLItemListener urlListener = new URLItemListener(getActivity());
            urlListView.setOnItemClickListener(urlListener);

            urlListView
                    .setAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, urls));
            urlDialog.setContentView(urlListView);
            urlDialog.show();

            return true;
        }
        case R.id.resize: {
            final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);

            final View resizeView = inflater.inflate(R.layout.dia_resize, null, false);
            new AlertDialog.Builder(getActivity()).setView(resizeView)
                    .setPositiveButton(R.string.button_resize, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            final 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;
                            }

                            terminalView.forceSize(width, height);
                        }
                    }).setNegativeButton(android.R.string.cancel, null).create().show();

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

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

        getActivity().setVolumeControlStream(AudioManager.STREAM_NOTIFICATION);

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

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

        mMenuDisconnect.setEnabled(activeTerminal);
        if (sessionOpen || !disconnected)
            mMenuDisconnect.setTitle(R.string.list_host_disconnect);
        else
            mMenuDisconnect.setTitle(R.string.console_menu_close);

        mMenuCopy.setEnabled(activeTerminal);
        mMenuPaste.setEnabled(clipboard.hasText() && sessionOpen);
        mMenuPortForwards.setEnabled(sessionOpen && canForwardPorts);
        mMenuUrlScan.setEnabled(activeTerminal);
        mMenuResize.setEnabled(sessionOpen);
    }

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

        getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }

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

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

        final TerminalManager bound = mListener.getTerminalManager();
        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)) {
            getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        } else {
            getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }

        // TODO: Orientation
        // configureOrientation();

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

    protected void startConsole(Uri requested) {
        /*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 = mListener.getTerminalManager().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 = mListener.getTerminalManager().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();
    }

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

            TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
            mListener.onTerminalViewChanged(terminalView.bridge.host);
        }
    }

    /**
     * Save the currently shown {@link org.connectbot.TerminalView} as the default. This is
     * saved back down into {@link org.connectbot.service.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;
        TerminalManager bound = mListener.getTerminalManager();
        if (bound == null)
            return;
        bound.defaultBridge = terminal.bridge;
    }

    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 org.connectbot.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;
            if (instructions != null && instructions.length() > 0) {
                stringPromptInstructions.setVisibility(View.VISIBLE);
                stringPromptInstructions.setText(instructions);
            } else
                stringPromptInstructions.setVisibility(View.GONE);
            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();
        }
    }

    private static class URLItemListener implements OnItemClickListener {
        private WeakReference<Context> contextRef;

        URLItemListener(Context context) {
            this.contextRef = new WeakReference<Context>(context);
        }

        public void onItemClick(AdapterView<?> arg0, View view, int position, long id) {
            final Context context = contextRef == null ? null : contextRef.get();

            if (context == null)
                return;

            try {
                final TextView urlView = (TextView) view;

                String url = urlView.getText().toString();
                if (url.indexOf("://") < 0)
                    url = "http://" + url;

                final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                context.startActivity(intent);
            } catch (Exception e) {
                Log.e(TAG, "couldn't open URL", e);
                // We should probably tell the user that we couldn't find a handler...
            }
        }

    }

    // TODO: Move to Main Activity
    /*@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(getActivity(), bridge);
        terminal.setId(R.id.console_flip);
        view.addView(terminal, 0);

        synchronized (flip) {
            // finally attach to the flipper
            flip.addView(view);
            flip.setVisibility(View.VISIBLE);
            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);
                TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
                mListener.onTerminalViewChanged(terminalView.bridge.host);
            } catch (NullPointerException npe) {
                Log.d(TAG, "View went away when we were about to display it", npe);
            }

            updatePromptVisible();
            updateEmptyVisible();
        }
    }
}