Home | History | Annotate | Download | only in connectbot
      1 /*
      2  * ConnectBot: simple, powerful, open-source SSH client for Android
      3  * Copyright 2007 Kenny Root, Jeffrey Sharkey
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 /**
     19  * @author modified by raaar
     20  *
     21  */
     22 
     23 package org.connectbot;
     24 
     25 import android.app.Activity;
     26 import android.app.AlertDialog;
     27 import android.content.ComponentName;
     28 import android.content.Context;
     29 import android.content.DialogInterface;
     30 import android.content.Intent;
     31 import android.content.ServiceConnection;
     32 import android.content.SharedPreferences;
     33 import android.content.pm.ActivityInfo;
     34 import android.content.res.Configuration;
     35 import android.media.AudioManager;
     36 import android.net.Uri;
     37 import android.os.Bundle;
     38 import android.os.Handler;
     39 import android.os.IBinder;
     40 import android.os.Message;
     41 import android.os.PowerManager;
     42 import android.preference.PreferenceManager;
     43 import android.text.ClipboardManager;
     44 import android.view.ContextMenu;
     45 import android.view.ContextMenu.ContextMenuInfo;
     46 import android.view.GestureDetector;
     47 import android.view.LayoutInflater;
     48 import android.view.Menu;
     49 import android.view.MenuItem;
     50 import android.view.MotionEvent;
     51 import android.view.View;
     52 import android.view.View.OnClickListener;
     53 import android.view.View.OnTouchListener;
     54 import android.view.ViewConfiguration;
     55 import android.view.WindowManager;
     56 import android.view.animation.Animation;
     57 import android.view.animation.AnimationUtils;
     58 import android.view.inputmethod.InputMethodManager;
     59 import android.widget.Button;
     60 import android.widget.EditText;
     61 import android.widget.ImageView;
     62 import android.widget.RelativeLayout;
     63 import android.widget.TextView;
     64 import android.widget.Toast;
     65 import android.widget.ViewFlipper;
     66 
     67 import com.googlecode.android_scripting.Constants;
     68 import com.googlecode.android_scripting.Log;
     69 import com.googlecode.android_scripting.R;
     70 import com.googlecode.android_scripting.ScriptProcess;
     71 import com.googlecode.android_scripting.activity.Preferences;
     72 import com.googlecode.android_scripting.activity.ScriptingLayerService;
     73 
     74 import de.mud.terminal.VDUBuffer;
     75 import de.mud.terminal.vt320;
     76 
     77 import org.connectbot.service.PromptHelper;
     78 import org.connectbot.service.TerminalBridge;
     79 import org.connectbot.service.TerminalManager;
     80 import org.connectbot.util.PreferenceConstants;
     81 import org.connectbot.util.SelectionArea;
     82 
     83 public class ConsoleActivity extends Activity {
     84 
     85   protected static final int REQUEST_EDIT = 1;
     86 
     87   private static final int CLICK_TIME = 250;
     88   private static final float MAX_CLICK_DISTANCE = 25f;
     89   private static final int KEYBOARD_DISPLAY_TIME = 1250;
     90 
     91   // Direction to shift the ViewFlipper
     92   private static final int SHIFT_LEFT = 0;
     93   private static final int SHIFT_RIGHT = 1;
     94 
     95   protected ViewFlipper flip = null;
     96   protected TerminalManager manager = null;
     97   protected ScriptingLayerService mService = null;
     98   protected LayoutInflater inflater = null;
     99 
    100   private SharedPreferences prefs = null;
    101 
    102   private PowerManager.WakeLock wakelock = null;
    103 
    104   protected Integer processID;
    105 
    106   protected ClipboardManager clipboard;
    107 
    108   private RelativeLayout booleanPromptGroup;
    109   private TextView booleanPrompt;
    110   private Button booleanYes, booleanNo;
    111 
    112   private Animation slide_left_in, slide_left_out, slide_right_in, slide_right_out,
    113       fade_stay_hidden, fade_out_delayed;
    114 
    115   private Animation keyboard_fade_in, keyboard_fade_out;
    116   private ImageView keyboardButton;
    117   private float lastX, lastY;
    118 
    119   private int mTouchSlopSquare;
    120 
    121   private InputMethodManager inputManager;
    122 
    123   protected TerminalBridge copySource = null;
    124   private int lastTouchRow, lastTouchCol;
    125 
    126   private boolean forcedOrientation;
    127 
    128   private Handler handler = new Handler();
    129 
    130   private static enum MenuId {
    131     EDIT, PREFS, EMAIL, RESIZE, COPY, PASTE;
    132     public int getId() {
    133       return ordinal() + Menu.FIRST;
    134     }
    135   }
    136 
    137   private final ServiceConnection mConnection = new ServiceConnection() {
    138     @Override
    139     public void onServiceConnected(ComponentName name, IBinder service) {
    140       mService = ((ScriptingLayerService.LocalBinder) service).getService();
    141       manager = mService.getTerminalManager();
    142       // let manager know about our event handling services
    143       manager.setDisconnectHandler(disconnectHandler);
    144 
    145       Log.d(String.format("Connected to TerminalManager and found bridges.size=%d", manager
    146           .getBridgeList().size()));
    147 
    148       manager.setResizeAllowed(true);
    149 
    150       // clear out any existing bridges and record requested index
    151       flip.removeAllViews();
    152 
    153       int requestedIndex = 0;
    154 
    155       TerminalBridge requestedBridge = manager.getConnectedBridge(processID);
    156 
    157       // If we didn't find the requested connection, try opening it
    158       if (processID != null && requestedBridge == null) {
    159         try {
    160           Log.d(String.format(
    161               "We couldnt find an existing bridge with id = %d, so creating one now", processID));
    162           requestedBridge = manager.openConnection(processID);
    163         } catch (Exception e) {
    164           Log.e("Problem while trying to create new requested bridge", e);
    165         }
    166       }
    167 
    168       // create views for all bridges on this service
    169       for (TerminalBridge bridge : manager.getBridgeList()) {
    170 
    171         final int currentIndex = addNewTerminalView(bridge);
    172 
    173         // check to see if this bridge was requested
    174         if (bridge == requestedBridge) {
    175           requestedIndex = currentIndex;
    176         }
    177       }
    178 
    179       setDisplayedTerminal(requestedIndex);
    180     }
    181 
    182     @Override
    183     public void onServiceDisconnected(ComponentName name) {
    184       manager = null;
    185       mService = null;
    186     }
    187   };
    188 
    189   protected Handler promptHandler = new Handler() {
    190     @Override
    191     public void handleMessage(Message msg) {
    192       // someone below us requested to display a prompt
    193       updatePromptVisible();
    194     }
    195   };
    196 
    197   protected Handler disconnectHandler = new Handler() {
    198     @Override
    199     public void handleMessage(Message msg) {
    200       Log.d("Someone sending HANDLE_DISCONNECT to parentHandler");
    201       TerminalBridge bridge = (TerminalBridge) msg.obj;
    202       closeBridge(bridge);
    203     }
    204   };
    205 
    206   /**
    207    * @param bridge
    208    */
    209   private void closeBridge(final TerminalBridge bridge) {
    210     synchronized (flip) {
    211       final int flipIndex = getFlipIndex(bridge);
    212 
    213       if (flipIndex >= 0) {
    214         if (flip.getDisplayedChild() == flipIndex) {
    215           shiftCurrentTerminal(SHIFT_LEFT);
    216         }
    217         flip.removeViewAt(flipIndex);
    218 
    219         /*
    220          * TODO Remove this workaround when ViewFlipper is fixed to listen to view removals. Android
    221          * Issue 1784
    222          */
    223         final int numChildren = flip.getChildCount();
    224         if (flip.getDisplayedChild() >= numChildren && numChildren > 0) {
    225           flip.setDisplayedChild(numChildren - 1);
    226         }
    227       }
    228 
    229       // If we just closed the last bridge, go back to the previous activity.
    230       if (flip.getChildCount() == 0) {
    231         finish();
    232       }
    233     }
    234   }
    235 
    236   protected View findCurrentView(int id) {
    237     View view = flip.getCurrentView();
    238     if (view == null) {
    239       return null;
    240     }
    241     return view.findViewById(id);
    242   }
    243 
    244   protected PromptHelper getCurrentPromptHelper() {
    245     View view = findCurrentView(R.id.console_flip);
    246     if (!(view instanceof TerminalView)) {
    247       return null;
    248     }
    249     return ((TerminalView) view).bridge.getPromptHelper();
    250   }
    251 
    252   protected void hideAllPrompts() {
    253     booleanPromptGroup.setVisibility(View.GONE);
    254   }
    255 
    256   @Override
    257   public void onCreate(Bundle icicle) {
    258     super.onCreate(icicle);
    259 
    260     this.setContentView(R.layout.act_console);
    261 
    262     clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
    263     prefs = PreferenceManager.getDefaultSharedPreferences(this);
    264 
    265     // hide status bar if requested by user
    266     if (prefs.getBoolean(PreferenceConstants.FULLSCREEN, false)) {
    267       getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    268           WindowManager.LayoutParams.FLAG_FULLSCREEN);
    269     }
    270 
    271     // TODO find proper way to disable volume key beep if it exists.
    272     setVolumeControlStream(AudioManager.STREAM_MUSIC);
    273 
    274     PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE);
    275     wakelock = manager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getPackageName());
    276 
    277     // handle requested console from incoming intent
    278     int id = getIntent().getIntExtra(Constants.EXTRA_PROXY_PORT, -1);
    279 
    280     if (id > 0) {
    281       processID = id;
    282     }
    283 
    284     inflater = LayoutInflater.from(this);
    285 
    286     flip = (ViewFlipper) findViewById(R.id.console_flip);
    287     booleanPromptGroup = (RelativeLayout) findViewById(R.id.console_boolean_group);
    288     booleanPrompt = (TextView) findViewById(R.id.console_prompt);
    289 
    290     booleanYes = (Button) findViewById(R.id.console_prompt_yes);
    291     booleanYes.setOnClickListener(new OnClickListener() {
    292       public void onClick(View v) {
    293         PromptHelper helper = getCurrentPromptHelper();
    294         if (helper == null) {
    295           return;
    296         }
    297         helper.setResponse(Boolean.TRUE);
    298         updatePromptVisible();
    299       }
    300     });
    301 
    302     booleanNo = (Button) findViewById(R.id.console_prompt_no);
    303     booleanNo.setOnClickListener(new OnClickListener() {
    304       public void onClick(View v) {
    305         PromptHelper helper = getCurrentPromptHelper();
    306         if (helper == null) {
    307           return;
    308         }
    309         helper.setResponse(Boolean.FALSE);
    310         updatePromptVisible();
    311       }
    312     });
    313 
    314     // preload animations for terminal switching
    315     slide_left_in = AnimationUtils.loadAnimation(this, R.anim.slide_left_in);
    316     slide_left_out = AnimationUtils.loadAnimation(this, R.anim.slide_left_out);
    317     slide_right_in = AnimationUtils.loadAnimation(this, R.anim.slide_right_in);
    318     slide_right_out = AnimationUtils.loadAnimation(this, R.anim.slide_right_out);
    319 
    320     fade_out_delayed = AnimationUtils.loadAnimation(this, R.anim.fade_out_delayed);
    321     fade_stay_hidden = AnimationUtils.loadAnimation(this, R.anim.fade_stay_hidden);
    322 
    323     // Preload animation for keyboard button
    324     keyboard_fade_in = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_in);
    325     keyboard_fade_out = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_out);
    326 
    327     inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    328     keyboardButton = (ImageView) findViewById(R.id.keyboard_button);
    329     keyboardButton.setOnClickListener(new OnClickListener() {
    330       public void onClick(View view) {
    331         View flip = findCurrentView(R.id.console_flip);
    332         if (flip == null) {
    333           return;
    334         }
    335 
    336         inputManager.showSoftInput(flip, InputMethodManager.SHOW_FORCED);
    337         keyboardButton.setVisibility(View.GONE);
    338       }
    339     });
    340     if (prefs.getBoolean(PreferenceConstants.HIDE_KEYBOARD, false)) {
    341       // Force hidden keyboard.
    342       getWindow().setSoftInputMode(
    343           WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
    344               | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    345     }
    346     final ViewConfiguration configuration = ViewConfiguration.get(this);
    347     int touchSlop = configuration.getScaledTouchSlop();
    348     mTouchSlopSquare = touchSlop * touchSlop;
    349 
    350     // detect fling gestures to switch between terminals
    351     final GestureDetector detect =
    352         new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
    353           private float totalY = 0;
    354 
    355           @Override
    356           public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    357 
    358             final float distx = e2.getRawX() - e1.getRawX();
    359             final float disty = e2.getRawY() - e1.getRawY();
    360             final int goalwidth = flip.getWidth() / 2;
    361 
    362             // need to slide across half of display to trigger console change
    363             // make sure user kept a steady hand horizontally
    364             if (Math.abs(disty) < (flip.getHeight() / 4)) {
    365               if (distx > goalwidth) {
    366                 shiftCurrentTerminal(SHIFT_RIGHT);
    367                 return true;
    368               }
    369 
    370               if (distx < -goalwidth) {
    371                 shiftCurrentTerminal(SHIFT_LEFT);
    372                 return true;
    373               }
    374 
    375             }
    376 
    377             return false;
    378           }
    379 
    380           @Override
    381           public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    382 
    383             // if copying, then ignore
    384             if (copySource != null && copySource.isSelectingForCopy()) {
    385               return false;
    386             }
    387 
    388             if (e1 == null || e2 == null) {
    389               return false;
    390             }
    391 
    392             // if releasing then reset total scroll
    393             if (e2.getAction() == MotionEvent.ACTION_UP) {
    394               totalY = 0;
    395             }
    396 
    397             // activate consider if within x tolerance
    398             if (Math.abs(e1.getX() - e2.getX()) < ViewConfiguration.getTouchSlop() * 4) {
    399 
    400               View flip = findCurrentView(R.id.console_flip);
    401               if (flip == null) {
    402                 return false;
    403               }
    404               TerminalView terminal = (TerminalView) flip;
    405 
    406               // estimate how many rows we have scrolled through
    407               // accumulate distance that doesn't trigger immediate scroll
    408               totalY += distanceY;
    409               final int moved = (int) (totalY / terminal.bridge.charHeight);
    410 
    411               VDUBuffer buffer = terminal.bridge.getVDUBuffer();
    412 
    413               // consume as scrollback only if towards right half of screen
    414               if (e2.getX() > flip.getWidth() / 2) {
    415                 if (moved != 0) {
    416                   int base = buffer.getWindowBase();
    417                   buffer.setWindowBase(base + moved);
    418                   totalY = 0;
    419                   return true;
    420                 }
    421               } else {
    422                 // otherwise consume as pgup/pgdown for every 5 lines
    423                 if (moved > 5) {
    424                   ((vt320) buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0);
    425                   terminal.bridge.tryKeyVibrate();
    426                   totalY = 0;
    427                   return true;
    428                 } else if (moved < -5) {
    429                   ((vt320) buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0);
    430                   terminal.bridge.tryKeyVibrate();
    431                   totalY = 0;
    432                   return true;
    433                 }
    434 
    435               }
    436 
    437             }
    438 
    439             return false;
    440           }
    441 
    442         });
    443 
    444     flip.setOnCreateContextMenuListener(this);
    445 
    446     flip.setOnTouchListener(new OnTouchListener() {
    447 
    448       public boolean onTouch(View v, MotionEvent event) {
    449 
    450         // when copying, highlight the area
    451         if (copySource != null && copySource.isSelectingForCopy()) {
    452           int row = (int) Math.floor(event.getY() / copySource.charHeight);
    453           int col = (int) Math.floor(event.getX() / copySource.charWidth);
    454 
    455           SelectionArea area = copySource.getSelectionArea();
    456 
    457           switch (event.getAction()) {
    458           case MotionEvent.ACTION_DOWN:
    459             // recording starting area
    460             if (area.isSelectingOrigin()) {
    461               area.setRow(row);
    462               area.setColumn(col);
    463               lastTouchRow = row;
    464               lastTouchCol = col;
    465               copySource.redraw();
    466             }
    467             return true;
    468           case MotionEvent.ACTION_MOVE:
    469             /*
    470              * ignore when user hasn't moved since last time so we can fine-tune with directional
    471              * pad
    472              */
    473             if (row == lastTouchRow && col == lastTouchCol) {
    474               return true;
    475             }
    476             // if the user moves, start the selection for other corner
    477             area.finishSelectingOrigin();
    478 
    479             // update selected area
    480             area.setRow(row);
    481             area.setColumn(col);
    482             lastTouchRow = row;
    483             lastTouchCol = col;
    484             copySource.redraw();
    485             return true;
    486           case MotionEvent.ACTION_UP:
    487             /*
    488              * If they didn't move their finger, maybe they meant to select the rest of the text
    489              * with the directional pad.
    490              */
    491             if (area.getLeft() == area.getRight() && area.getTop() == area.getBottom()) {
    492               return true;
    493             }
    494 
    495             // copy selected area to clipboard
    496             String copiedText = area.copyFrom(copySource.getVDUBuffer());
    497 
    498             clipboard.setText(copiedText);
    499             Toast.makeText(ConsoleActivity.this,
    500                 getString(R.string.terminal_copy_done, copiedText.length()), Toast.LENGTH_LONG)
    501                 .show();
    502             // fall through to clear state
    503 
    504           case MotionEvent.ACTION_CANCEL:
    505             // make sure we clear any highlighted area
    506             area.reset();
    507             copySource.setSelectingForCopy(false);
    508             copySource.redraw();
    509             return true;
    510           }
    511         }
    512 
    513         Configuration config = getResources().getConfiguration();
    514 
    515         if (event.getAction() == MotionEvent.ACTION_DOWN) {
    516           lastX = event.getX();
    517           lastY = event.getY();
    518         } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
    519           final int deltaX = (int) (lastX - event.getX());
    520           final int deltaY = (int) (lastY - event.getY());
    521           int distance = (deltaX * deltaX) + (deltaY * deltaY);
    522           if (distance > mTouchSlopSquare) {
    523             // If currently scheduled long press event is not canceled here,
    524             // GestureDetector.onScroll is executed, which takes a while, and by the time we are
    525             // back in the view's dispatchTouchEvent
    526             // mPendingCheckForLongPress is already executed
    527             flip.cancelLongPress();
    528           }
    529         } else if (event.getAction() == MotionEvent.ACTION_UP) {
    530           // Same as above, except now GestureDetector.onFling is called.
    531           flip.cancelLongPress();
    532           if (config.hardKeyboardHidden != Configuration.KEYBOARDHIDDEN_NO
    533               && keyboardButton.getVisibility() == View.GONE
    534               && event.getEventTime() - event.getDownTime() < CLICK_TIME
    535               && Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE
    536               && Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) {
    537             keyboardButton.startAnimation(keyboard_fade_in);
    538             keyboardButton.setVisibility(View.VISIBLE);
    539 
    540             handler.postDelayed(new Runnable() {
    541               public void run() {
    542                 if (keyboardButton.getVisibility() == View.GONE) {
    543                   return;
    544                 }
    545 
    546                 keyboardButton.startAnimation(keyboard_fade_out);
    547                 keyboardButton.setVisibility(View.GONE);
    548               }
    549             }, KEYBOARD_DISPLAY_TIME);
    550 
    551             return false;
    552           }
    553         }
    554         // pass any touch events back to detector
    555         return detect.onTouchEvent(event);
    556       }
    557 
    558     });
    559 
    560   }
    561 
    562   private void configureOrientation() {
    563     String rotateDefault;
    564     if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS) {
    565       rotateDefault = PreferenceConstants.ROTATION_PORTRAIT;
    566     } else {
    567       rotateDefault = PreferenceConstants.ROTATION_LANDSCAPE;
    568     }
    569 
    570     String rotate = prefs.getString(PreferenceConstants.ROTATION, rotateDefault);
    571     if (PreferenceConstants.ROTATION_DEFAULT.equals(rotate)) {
    572       rotate = rotateDefault;
    573     }
    574 
    575     // request a forced orientation if requested by user
    576     if (PreferenceConstants.ROTATION_LANDSCAPE.equals(rotate)) {
    577       setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    578       forcedOrientation = true;
    579     } else if (PreferenceConstants.ROTATION_PORTRAIT.equals(rotate)) {
    580       setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    581       forcedOrientation = true;
    582     } else {
    583       setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
    584       forcedOrientation = false;
    585     }
    586   }
    587 
    588   @Override
    589   public boolean onCreateOptionsMenu(Menu menu) {
    590     super.onCreateOptionsMenu(menu);
    591     getMenuInflater().inflate(R.menu.terminal, menu);
    592     menu.setQwertyMode(true);
    593     return true;
    594   }
    595 
    596   @Override
    597   public boolean onPrepareOptionsMenu(Menu menu) {
    598     super.onPrepareOptionsMenu(menu);
    599     setVolumeControlStream(AudioManager.STREAM_NOTIFICATION);
    600     TerminalBridge bridge = ((TerminalView) findCurrentView(R.id.console_flip)).bridge;
    601     boolean sessionOpen = bridge.isSessionOpen();
    602     menu.findItem(R.id.terminal_menu_resize).setEnabled(sessionOpen);
    603     if (bridge.getProcess() instanceof ScriptProcess) {
    604       menu.findItem(R.id.terminal_menu_exit_and_edit).setEnabled(true);
    605     }
    606     bridge.onPrepareOptionsMenu(menu);
    607     return true;
    608   }
    609 
    610   @Override
    611   public boolean onOptionsItemSelected(MenuItem item) {
    612     if (item.getItemId() == R.id.terminal_menu_resize) {
    613       doResize();
    614     } else if (item.getItemId() == R.id.terminal_menu_preferences) {
    615       doPreferences();
    616     } else if (item.getItemId() == R.id.terminal_menu_send_email) {
    617       doEmailTranscript();
    618     } else if (item.getItemId() == R.id.terminal_menu_exit_and_edit) {
    619       TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
    620       TerminalBridge bridge = terminalView.bridge;
    621       if (manager != null) {
    622         manager.closeConnection(bridge, true);
    623       } else {
    624         Intent intent = new Intent(this, ScriptingLayerService.class);
    625         intent.setAction(Constants.ACTION_KILL_PROCESS);
    626         intent.putExtra(Constants.EXTRA_PROXY_PORT, bridge.getId());
    627         startService(intent);
    628         Message.obtain(disconnectHandler, -1, bridge).sendToTarget();
    629       }
    630       Intent intent = new Intent(Constants.ACTION_EDIT_SCRIPT);
    631       ScriptProcess process = (ScriptProcess) bridge.getProcess();
    632       intent.putExtra(Constants.EXTRA_SCRIPT_PATH, process.getPath());
    633       startActivity(intent);
    634       finish();
    635     }
    636     return super.onOptionsItemSelected(item);
    637   }
    638 
    639   @Override
    640   public void onOptionsMenuClosed(Menu menu) {
    641     super.onOptionsMenuClosed(menu);
    642     setVolumeControlStream(AudioManager.STREAM_MUSIC);
    643   }
    644 
    645   private void doResize() {
    646     closeOptionsMenu();
    647     final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
    648     final View resizeView = inflater.inflate(R.layout.dia_resize, null, false);
    649     new AlertDialog.Builder(ConsoleActivity.this).setView(resizeView)
    650         .setPositiveButton(R.string.button_resize, new DialogInterface.OnClickListener() {
    651           public void onClick(DialogInterface dialog, int which) {
    652             int width, height;
    653             try {
    654               width =
    655                   Integer.parseInt(((EditText) resizeView.findViewById(R.id.width)).getText()
    656                       .toString());
    657               height =
    658                   Integer.parseInt(((EditText) resizeView.findViewById(R.id.height)).getText()
    659                       .toString());
    660             } catch (NumberFormatException nfe) {
    661               return;
    662             }
    663             terminalView.forceSize(width, height);
    664           }
    665         }).setNegativeButton(android.R.string.cancel, null).create().show();
    666   }
    667 
    668   private void doPreferences() {
    669     startActivity(new Intent(this, Preferences.class));
    670   }
    671 
    672   private void doEmailTranscript() {
    673     // Don't really want to supply an address, but currently it's required,
    674     // otherwise we get an exception.
    675     TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
    676     TerminalBridge bridge = terminalView.bridge;
    677     // TODO(raaar): Replace with process log.
    678     VDUBuffer buffer = bridge.getVDUBuffer();
    679     int height = buffer.getRows();
    680     int width = buffer.getColumns();
    681     StringBuilder string = new StringBuilder();
    682     for (int i = 0; i < height; i++) {
    683       for (int j = 0; j < width; j++) {
    684         string.append(buffer.getChar(j, i));
    685       }
    686     }
    687     String addr = "user (at) example.com";
    688     Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + addr));
    689     intent.putExtra("body", string.toString().trim());
    690     startActivity(intent);
    691   }
    692 
    693   @Override
    694   public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
    695     TerminalBridge bridge = ((TerminalView) findCurrentView(R.id.console_flip)).bridge;
    696     boolean sessionOpen = bridge.isSessionOpen();
    697     menu.add(Menu.NONE, MenuId.COPY.getId(), Menu.NONE, R.string.terminal_menu_copy);
    698     if (clipboard.hasText() && sessionOpen) {
    699       menu.add(Menu.NONE, MenuId.PASTE.getId(), Menu.NONE, R.string.terminal_menu_paste);
    700     }
    701     bridge.onCreateContextMenu(menu, view, menuInfo);
    702   }
    703 
    704   @Override
    705   public boolean onContextItemSelected(MenuItem item) {
    706     int itemId = item.getItemId();
    707     if (itemId == MenuId.COPY.getId()) {
    708       TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
    709       copySource = terminalView.bridge;
    710       SelectionArea area = copySource.getSelectionArea();
    711       area.reset();
    712       area.setBounds(copySource.getVDUBuffer().getColumns(), copySource.getVDUBuffer().getRows());
    713       copySource.setSelectingForCopy(true);
    714       // Make sure we show the initial selection
    715       copySource.redraw();
    716       Toast.makeText(ConsoleActivity.this, getString(R.string.terminal_copy_start),
    717           Toast.LENGTH_LONG).show();
    718       return true;
    719     } else if (itemId == MenuId.PASTE.getId()) {
    720       TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip);
    721       TerminalBridge bridge = terminalView.bridge;
    722       // pull string from clipboard and generate all events to force down
    723       String clip = clipboard.getText().toString();
    724       bridge.injectString(clip);
    725       return true;
    726     }
    727     return false;
    728   }
    729 
    730   @Override
    731   public void onStart() {
    732     super.onStart();
    733     // connect with manager service to find all bridges
    734     // when connected it will insert all views
    735     bindService(new Intent(this, ScriptingLayerService.class), mConnection, 0);
    736   }
    737 
    738   @Override
    739   public void onPause() {
    740     super.onPause();
    741     Log.d("onPause called");
    742 
    743     // Allow the screen to dim and fall asleep.
    744     if (wakelock != null && wakelock.isHeld()) {
    745       wakelock.release();
    746     }
    747 
    748     if (forcedOrientation && manager != null) {
    749       manager.setResizeAllowed(false);
    750     }
    751   }
    752 
    753   @Override
    754   public void onResume() {
    755     super.onResume();
    756     Log.d("onResume called");
    757 
    758     // Make sure we don't let the screen fall asleep.
    759     // This also keeps the Wi-Fi chipset from disconnecting us.
    760     if (wakelock != null && prefs.getBoolean(PreferenceConstants.KEEP_ALIVE, true)) {
    761       wakelock.acquire();
    762     }
    763 
    764     configureOrientation();
    765 
    766     if (forcedOrientation && manager != null) {
    767       manager.setResizeAllowed(true);
    768     }
    769   }
    770 
    771   /*
    772    * (non-Javadoc)
    773    *
    774    * @see android.app.Activity#onNewIntent(android.content.Intent)
    775    */
    776   @Override
    777   protected void onNewIntent(Intent intent) {
    778     super.onNewIntent(intent);
    779 
    780     Log.d("onNewIntent called");
    781 
    782     int id = intent.getIntExtra(Constants.EXTRA_PROXY_PORT, -1);
    783 
    784     if (id > 0) {
    785       processID = id;
    786     }
    787 
    788     if (processID == null) {
    789       Log.e("Got null intent data in onNewIntent()");
    790       return;
    791     }
    792 
    793     if (manager == null) {
    794       Log.e("We're not bound in onNewIntent()");
    795       return;
    796     }
    797 
    798     TerminalBridge requestedBridge = manager.getConnectedBridge(processID);
    799     int requestedIndex = 0;
    800 
    801     synchronized (flip) {
    802       if (requestedBridge == null) {
    803         // If we didn't find the requested connection, try opening it
    804 
    805         try {
    806           Log.d(String.format("We couldnt find an existing bridge with id = %d,"
    807               + "so creating one now", processID));
    808           requestedBridge = manager.openConnection(processID);
    809         } catch (Exception e) {
    810           Log.e("Problem while trying to create new requested bridge", e);
    811         }
    812 
    813         requestedIndex = addNewTerminalView(requestedBridge);
    814       } else {
    815         final int flipIndex = getFlipIndex(requestedBridge);
    816         if (flipIndex > requestedIndex) {
    817           requestedIndex = flipIndex;
    818         }
    819       }
    820 
    821       setDisplayedTerminal(requestedIndex);
    822     }
    823   }
    824 
    825   @Override
    826   public void onStop() {
    827     super.onStop();
    828     unbindService(mConnection);
    829   }
    830 
    831   protected void shiftCurrentTerminal(final int direction) {
    832     View overlay;
    833     synchronized (flip) {
    834       boolean shouldAnimate = flip.getChildCount() > 1;
    835 
    836       // Only show animation if there is something else to go to.
    837       if (shouldAnimate) {
    838         // keep current overlay from popping up again
    839         overlay = findCurrentView(R.id.terminal_overlay);
    840         if (overlay != null) {
    841           overlay.startAnimation(fade_stay_hidden);
    842         }
    843 
    844         if (direction == SHIFT_LEFT) {
    845           flip.setInAnimation(slide_left_in);
    846           flip.setOutAnimation(slide_left_out);
    847           flip.showNext();
    848         } else if (direction == SHIFT_RIGHT) {
    849           flip.setInAnimation(slide_right_in);
    850           flip.setOutAnimation(slide_right_out);
    851           flip.showPrevious();
    852         }
    853       }
    854 
    855       if (shouldAnimate) {
    856         // show overlay on new slide and start fade
    857         overlay = findCurrentView(R.id.terminal_overlay);
    858         if (overlay != null) {
    859           overlay.startAnimation(fade_out_delayed);
    860         }
    861       }
    862 
    863       updatePromptVisible();
    864     }
    865   }
    866 
    867   /**
    868    * Show any prompts requested by the currently visible {@link TerminalView}.
    869    */
    870   protected void updatePromptVisible() {
    871     // check if our currently-visible terminalbridge is requesting any prompt services
    872     View view = findCurrentView(R.id.console_flip);
    873 
    874     // Hide all the prompts in case a prompt request was canceled
    875     hideAllPrompts();
    876 
    877     if (!(view instanceof TerminalView)) {
    878       // we dont have an active view, so hide any prompts
    879       return;
    880     }
    881 
    882     PromptHelper prompt = ((TerminalView) view).bridge.getPromptHelper();
    883 
    884     if (Boolean.class.equals(prompt.promptRequested)) {
    885       booleanPromptGroup.setVisibility(View.VISIBLE);
    886       booleanPrompt.setText(prompt.promptHint);
    887       booleanYes.requestFocus();
    888     } else {
    889       hideAllPrompts();
    890       view.requestFocus();
    891     }
    892   }
    893 
    894   @Override
    895   public void onConfigurationChanged(Configuration newConfig) {
    896     super.onConfigurationChanged(newConfig);
    897 
    898     Log.d(String.format(
    899         "onConfigurationChanged; requestedOrientation=%d, newConfig.orientation=%d",
    900         getRequestedOrientation(), newConfig.orientation));
    901     if (manager != null) {
    902       if (forcedOrientation
    903           && (newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
    904           || (newConfig.orientation != Configuration.ORIENTATION_PORTRAIT && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)) {
    905         manager.setResizeAllowed(false);
    906       } else {
    907         manager.setResizeAllowed(true);
    908       }
    909 
    910       manager
    911           .setHardKeyboardHidden(newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES);
    912     }
    913   }
    914 
    915   /**
    916    * Adds a new TerminalBridge to the current set of views in our ViewFlipper.
    917    *
    918    * @param bridge
    919    *          TerminalBridge to add to our ViewFlipper
    920    * @return the child index of the new view in the ViewFlipper
    921    */
    922   private int addNewTerminalView(TerminalBridge bridge) {
    923     // let them know about our prompt handler services
    924     bridge.getPromptHelper().setHandler(promptHandler);
    925 
    926     // inflate each terminal view
    927     RelativeLayout view = (RelativeLayout) inflater.inflate(R.layout.item_terminal, flip, false);
    928 
    929     // set the terminal overlay text
    930     TextView overlay = (TextView) view.findViewById(R.id.terminal_overlay);
    931     overlay.setText(bridge.getName());
    932 
    933     // and add our terminal view control, using index to place behind overlay
    934     TerminalView terminal = new TerminalView(ConsoleActivity.this, bridge);
    935     terminal.setId(R.id.console_flip);
    936     view.addView(terminal, 0);
    937 
    938     synchronized (flip) {
    939       // finally attach to the flipper
    940       flip.addView(view);
    941       return flip.getChildCount() - 1;
    942     }
    943   }
    944 
    945   private int getFlipIndex(TerminalBridge bridge) {
    946     synchronized (flip) {
    947       final int children = flip.getChildCount();
    948       for (int i = 0; i < children; i++) {
    949         final View view = flip.getChildAt(i).findViewById(R.id.console_flip);
    950 
    951         if (view == null || !(view instanceof TerminalView)) {
    952           // How did that happen?
    953           continue;
    954         }
    955 
    956         final TerminalView tv = (TerminalView) view;
    957 
    958         if (tv.bridge == bridge) {
    959           return i;
    960         }
    961       }
    962     }
    963 
    964     return -1;
    965   }
    966 
    967   /**
    968    * Displays the child in the ViewFlipper at the requestedIndex and updates the prompts.
    969    *
    970    * @param requestedIndex
    971    *          the index of the terminal view to display
    972    */
    973   private void setDisplayedTerminal(int requestedIndex) {
    974     synchronized (flip) {
    975       try {
    976         // show the requested bridge if found, also fade out overlay
    977         flip.setDisplayedChild(requestedIndex);
    978         flip.getCurrentView().findViewById(R.id.terminal_overlay).startAnimation(fade_out_delayed);
    979       } catch (NullPointerException npe) {
    980         Log.d("View went away when we were about to display it", npe);
    981       }
    982       updatePromptVisible();
    983     }
    984   }
    985 }
    986