Java程序  |  985行  |  32.75 KB

/*
 * 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 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.os.PowerManager;
import android.preference.PreferenceManager;
import android.text.ClipboardManager;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
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 com.googlecode.android_scripting.Constants;
import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.R;
import com.googlecode.android_scripting.ScriptProcess;
import com.googlecode.android_scripting.activity.Preferences;
import com.googlecode.android_scripting.activity.ScriptingLayerService;

import de.mud.terminal.VDUBuffer;
import de.mud.terminal.vt320;

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

public class ConsoleActivity extends Activity {

  protected static final int REQUEST_EDIT = 1;

  private static final int CLICK_TIME = 250;
  private static final float MAX_CLICK_DISTANCE = 25f;
  private static final int KEYBOARD_DISPLAY_TIME = 1250;

  // 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 manager = null;
  protected ScriptingLayerService mService = null;
  protected LayoutInflater inflater = null;

  private SharedPreferences prefs = null;

  private PowerManager.WakeLock wakelock = null;

  protected Integer processID;

  protected ClipboardManager clipboard;

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

  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 ImageView keyboardButton;
  private float lastX, lastY;

  private int mTouchSlopSquare;

  private InputMethodManager inputManager;

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

  private boolean forcedOrientation;

  private Handler handler = new Handler();

  private static enum MenuId {
    EDIT, PREFS, EMAIL, RESIZE, COPY, PASTE;
    public int getId() {
      return ordinal() + Menu.FIRST;
    }
  }

  private final ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
      mService = ((ScriptingLayerService.LocalBinder) service).getService();
      manager = mService.getTerminalManager();
      // let manager know about our event handling services
      manager.setDisconnectHandler(disconnectHandler);

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

      manager.setResizeAllowed(true);

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

      int requestedIndex = 0;

      TerminalBridge requestedBridge = manager.getConnectedBridge(processID);

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

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

        final int currentIndex = addNewTerminalView(bridge);

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

      setDisplayedTerminal(requestedIndex);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
      manager = null;
      mService = 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("Someone sending HANDLE_DISCONNECT to parentHandler");
      TerminalBridge bridge = (TerminalBridge) msg.obj;
      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);
        }
      }

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

  protected void hideAllPrompts() {
    booleanPromptGroup.setVisibility(View.GONE);
  }

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

    this.setContentView(R.layout.act_console);

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

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

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

    PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE);
    wakelock = manager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getPackageName());

    // handle requested console from incoming intent
    int id = getIntent().getIntExtra(Constants.EXTRA_PROXY_PORT, -1);

    if (id > 0) {
      processID = id;
    }

    inflater = LayoutInflater.from(this);

    flip = (ViewFlipper) findViewById(R.id.console_flip);
    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);
    keyboardButton = (ImageView) findViewById(R.id.keyboard_button);
    keyboardButton.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);
        keyboardButton.setVisibility(View.GONE);
      }
    });
    if (prefs.getBoolean(PreferenceConstants.HIDE_KEYBOARD, false)) {
      // Force hidden keyboard.
      getWindow().setSoftInputMode(
          WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
              | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    }
    final ViewConfiguration configuration = ViewConfiguration.get(this);
    int touchSlop = configuration.getScaledTouchSlop();
    mTouchSlopSquare = touchSlop * touchSlop;

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

              VDUBuffer buffer = terminal.bridge.getVDUBuffer();

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

              }

            }

            return false;
          }

        });

    flip.setOnCreateContextMenuListener(this);

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

            clipboard.setText(copiedText);
            Toast.makeText(ConsoleActivity.this,
                getString(R.string.terminal_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_MOVE) {
          final int deltaX = (int) (lastX - event.getX());
          final int deltaY = (int) (lastY - event.getY());
          int distance = (deltaX * deltaX) + (deltaY * deltaY);
          if (distance > mTouchSlopSquare) {
            // If currently scheduled long press event is not canceled here,
            // GestureDetector.onScroll is executed, which takes a while, and by the time we are
            // back in the view's dispatchTouchEvent
            // mPendingCheckForLongPress is already executed
            flip.cancelLongPress();
          }
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
          // Same as above, except now GestureDetector.onFling is called.
          flip.cancelLongPress();
          if (config.hardKeyboardHidden != Configuration.KEYBOARDHIDDEN_NO
              && keyboardButton.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) {
            keyboardButton.startAnimation(keyboard_fade_in);
            keyboardButton.setVisibility(View.VISIBLE);

            handler.postDelayed(new Runnable() {
              public void run() {
                if (keyboardButton.getVisibility() == View.GONE) {
                  return;
                }

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

            return false;
          }
        }
        // pass any touch events back to detector
        return detect.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);
    getMenuInflater().inflate(R.menu.terminal, menu);
    menu.setQwertyMode(true);
    return true;
  }

  @Override
  public boolean onPrepareOptionsMenu(Menu menu) {
    super.onPrepareOptionsMenu(menu);
    setVolumeControlStream(AudioManager.STREAM_NOTIFICATION);
    TerminalBridge bridge = ((TerminalView) findCurrentView(R.id.console_flip)).bridge;
    boolean sessionOpen = bridge.isSessionOpen();
    menu.findItem(R.id.terminal_menu_resize).setEnabled(sessionOpen);
    if (bridge.getProcess() instanceof ScriptProcess) {
      menu.findItem(R.id.terminal_menu_exit_and_edit).setEnabled(true);
    }
    bridge.onPrepareOptionsMenu(menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == R.id.terminal_menu_resize) {
      doResize();
    } else if (item.getItemId() == R.id.terminal_menu_preferences) {
      doPreferences();
    } else if (item.getItemId() == R.id.terminal_menu_send_email) {
      doEmailTranscript();
    } else if (item.getItemId() == R.id.terminal_menu_exit_and_edit) {
      TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
      TerminalBridge bridge = terminalView.bridge;
      if (manager != null) {
        manager.closeConnection(bridge, true);
      } else {
        Intent intent = new Intent(this, ScriptingLayerService.class);
        intent.setAction(Constants.ACTION_KILL_PROCESS);
        intent.putExtra(Constants.EXTRA_PROXY_PORT, bridge.getId());
        startService(intent);
        Message.obtain(disconnectHandler, -1, bridge).sendToTarget();
      }
      Intent intent = new Intent(Constants.ACTION_EDIT_SCRIPT);
      ScriptProcess process = (ScriptProcess) bridge.getProcess();
      intent.putExtra(Constants.EXTRA_SCRIPT_PATH, process.getPath());
      startActivity(intent);
      finish();
    }
    return super.onOptionsItemSelected(item);
  }

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

  private void doResize() {
    closeOptionsMenu();
    final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
    final View resizeView = inflater.inflate(R.layout.dia_resize, null, false);
    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) {
              return;
            }
            terminalView.forceSize(width, height);
          }
        }).setNegativeButton(android.R.string.cancel, null).create().show();
  }

  private void doPreferences() {
    startActivity(new Intent(this, Preferences.class));
  }

  private void doEmailTranscript() {
    // Don't really want to supply an address, but currently it's required,
    // otherwise we get an exception.
    TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
    TerminalBridge bridge = terminalView.bridge;
    // TODO(raaar): Replace with process log.
    VDUBuffer buffer = bridge.getVDUBuffer();
    int height = buffer.getRows();
    int width = buffer.getColumns();
    StringBuilder string = new StringBuilder();
    for (int i = 0; i < height; i++) {
      for (int j = 0; j < width; j++) {
        string.append(buffer.getChar(j, i));
      }
    }
    String addr = "user@example.com";
    Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + addr));
    intent.putExtra("body", string.toString().trim());
    startActivity(intent);
  }

  @Override
  public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
    TerminalBridge bridge = ((TerminalView) findCurrentView(R.id.console_flip)).bridge;
    boolean sessionOpen = bridge.isSessionOpen();
    menu.add(Menu.NONE, MenuId.COPY.getId(), Menu.NONE, R.string.terminal_menu_copy);
    if (clipboard.hasText() && sessionOpen) {
      menu.add(Menu.NONE, MenuId.PASTE.getId(), Menu.NONE, R.string.terminal_menu_paste);
    }
    bridge.onCreateContextMenu(menu, view, menuInfo);
  }

  @Override
  public boolean onContextItemSelected(MenuItem item) {
    int itemId = item.getItemId();
    if (itemId == MenuId.COPY.getId()) {
      TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
      copySource = terminalView.bridge;
      SelectionArea area = copySource.getSelectionArea();
      area.reset();
      area.setBounds(copySource.getVDUBuffer().getColumns(), copySource.getVDUBuffer().getRows());
      copySource.setSelectingForCopy(true);
      // Make sure we show the initial selection
      copySource.redraw();
      Toast.makeText(ConsoleActivity.this, getString(R.string.terminal_copy_start),
          Toast.LENGTH_LONG).show();
      return true;
    } else if (itemId == MenuId.PASTE.getId()) {
      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;
    }
    return false;
  }

  @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, ScriptingLayerService.class), mConnection, 0);
  }

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

    // Allow the screen to dim and fall asleep.
    if (wakelock != null && wakelock.isHeld()) {
      wakelock.release();
    }

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

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

    // Make sure we don't let the screen fall asleep.
    // This also keeps the Wi-Fi chipset from disconnecting us.
    if (wakelock != null && prefs.getBoolean(PreferenceConstants.KEEP_ALIVE, true)) {
      wakelock.acquire();
    }

    configureOrientation();

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

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

    Log.d("onNewIntent called");

    int id = intent.getIntExtra(Constants.EXTRA_PROXY_PORT, -1);

    if (id > 0) {
      processID = id;
    }

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

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

    TerminalBridge requestedBridge = manager.getConnectedBridge(processID);
    int requestedIndex = 0;

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

        try {
          Log.d(String.format("We couldnt find an existing bridge with id = %d,"
              + "so creating one now", processID));
          requestedBridge = manager.openConnection(processID);
        } catch (Exception e) {
          Log.e("Problem while trying to create new requested bridge", e);
        }

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

      setDisplayedTerminal(requestedIndex);
    }
  }

  @Override
  public void onStop() {
    super.onStop();
    unbindService(mConnection);
  }

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

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

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

    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(String.format(
        "onConfigurationChanged; requestedOrientation=%d, newConfig.orientation=%d",
        getRequestedOrientation(), newConfig.orientation));
    if (manager != null) {
      if (forcedOrientation
          && (newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
          || (newConfig.orientation != Configuration.ORIENTATION_PORTRAIT && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)) {
        manager.setResizeAllowed(false);
      } else {
        manager.setResizeAllowed(true);
      }

      manager
          .setHardKeyboardHidden(newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES);
    }
  }

  /**
   * 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.getPromptHelper().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.getName());

    // 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("View went away when we were about to display it", npe);
      }
      updatePromptVisible();
    }
  }
}