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 * 20 */ 21 22 package org.connectbot; 23 24 import android.app.Activity; 25 import android.app.AlertDialog; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.Intent; 30 import android.content.ServiceConnection; 31 import android.content.SharedPreferences; 32 import android.content.pm.ActivityInfo; 33 import android.content.res.Configuration; 34 import android.media.AudioManager; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.os.IBinder; 39 import android.os.Message; 40 import android.os.PowerManager; 41 import android.preference.PreferenceManager; 42 import android.text.ClipboardManager; 43 import android.view.ContextMenu; 44 import android.view.ContextMenu.ContextMenuInfo; 45 import android.view.GestureDetector; 46 import android.view.LayoutInflater; 47 import android.view.Menu; 48 import android.view.MenuItem; 49 import android.view.MotionEvent; 50 import android.view.View; 51 import android.view.View.OnClickListener; 52 import android.view.View.OnTouchListener; 53 import android.view.ViewConfiguration; 54 import android.view.WindowManager; 55 import android.view.animation.Animation; 56 import android.view.animation.AnimationUtils; 57 import android.view.inputmethod.InputMethodManager; 58 import android.widget.Button; 59 import android.widget.EditText; 60 import android.widget.ImageView; 61 import android.widget.RelativeLayout; 62 import android.widget.TextView; 63 import android.widget.Toast; 64 import android.widget.ViewFlipper; 65 66 import com.googlecode.android_scripting.Constants; 67 import com.googlecode.android_scripting.Log; 68 import com.googlecode.android_scripting.R; 69 import com.googlecode.android_scripting.ScriptProcess; 70 import com.googlecode.android_scripting.activity.Preferences; 71 import com.googlecode.android_scripting.service.ScriptingLayerService; 72 73 import de.mud.terminal.VDUBuffer; 74 import de.mud.terminal.vt320; 75 76 import org.connectbot.service.PromptHelper; 77 import org.connectbot.service.TerminalBridge; 78 import org.connectbot.service.TerminalManager; 79 import org.connectbot.util.PreferenceConstants; 80 import org.connectbot.util.SelectionArea; 81 82 public class ConsoleActivity extends Activity { 83 84 protected static final int REQUEST_EDIT = 1; 85 86 private static final int CLICK_TIME = 250; 87 private static final float MAX_CLICK_DISTANCE = 25f; 88 private static final int KEYBOARD_DISPLAY_TIME = 1250; 89 90 // Direction to shift the ViewFlipper 91 private static final int SHIFT_LEFT = 0; 92 private static final int SHIFT_RIGHT = 1; 93 94 protected ViewFlipper flip = null; 95 protected TerminalManager manager = null; 96 protected ScriptingLayerService mService = null; 97 protected LayoutInflater inflater = null; 98 99 private SharedPreferences prefs = null; 100 101 private PowerManager.WakeLock wakelock = null; 102 103 protected Integer processID; 104 105 protected ClipboardManager clipboard; 106 107 private RelativeLayout booleanPromptGroup; 108 private TextView booleanPrompt; 109 private Button booleanYes, booleanNo; 110 111 private Animation slide_left_in, slide_left_out, slide_right_in, slide_right_out, 112 fade_stay_hidden, fade_out_delayed; 113 114 private Animation keyboard_fade_in, keyboard_fade_out; 115 private ImageView keyboardButton; 116 private float lastX, lastY; 117 118 private int mTouchSlopSquare; 119 120 private InputMethodManager inputManager; 121 122 protected TerminalBridge copySource = null; 123 private int lastTouchRow, lastTouchCol; 124 125 private boolean forcedOrientation; 126 127 private Handler handler = new Handler(); 128 129 private static enum MenuId { 130 EDIT, PREFS, EMAIL, RESIZE, COPY, PASTE; getId()131 public int getId() { 132 return ordinal() + Menu.FIRST; 133 } 134 } 135 136 private final ServiceConnection mConnection = new ServiceConnection() { 137 @Override 138 public void onServiceConnected(ComponentName name, IBinder service) { 139 mService = ((ScriptingLayerService.LocalBinder) service).getService(); 140 manager = mService.getTerminalManager(); 141 // let manager know about our event handling services 142 manager.setDisconnectHandler(disconnectHandler); 143 144 Log.d(String.format("Connected to TerminalManager and found bridges.size=%d", manager 145 .getBridgeList().size())); 146 147 manager.setResizeAllowed(true); 148 149 // clear out any existing bridges and record requested index 150 flip.removeAllViews(); 151 152 int requestedIndex = 0; 153 154 TerminalBridge requestedBridge = manager.getConnectedBridge(processID); 155 156 // If we didn't find the requested connection, try opening it 157 if (processID != null && requestedBridge == null) { 158 try { 159 Log.d(String.format( 160 "We couldnt find an existing bridge with id = %d, so creating one now", processID)); 161 requestedBridge = manager.openConnection(processID); 162 } catch (Exception e) { 163 Log.e("Problem while trying to create new requested bridge", e); 164 } 165 } 166 167 // create views for all bridges on this service 168 for (TerminalBridge bridge : manager.getBridgeList()) { 169 170 final int currentIndex = addNewTerminalView(bridge); 171 172 // check to see if this bridge was requested 173 if (bridge == requestedBridge) { 174 requestedIndex = currentIndex; 175 } 176 } 177 178 setDisplayedTerminal(requestedIndex); 179 } 180 181 @Override 182 public void onServiceDisconnected(ComponentName name) { 183 manager = null; 184 mService = null; 185 } 186 }; 187 188 protected Handler promptHandler = new Handler() { 189 @Override 190 public void handleMessage(Message msg) { 191 // someone below us requested to display a prompt 192 updatePromptVisible(); 193 } 194 }; 195 196 protected Handler disconnectHandler = new Handler() { 197 @Override 198 public void handleMessage(Message msg) { 199 Log.d("Someone sending HANDLE_DISCONNECT to parentHandler"); 200 TerminalBridge bridge = (TerminalBridge) msg.obj; 201 closeBridge(bridge); 202 } 203 }; 204 205 /** 206 * @param bridge 207 */ closeBridge(final TerminalBridge bridge)208 private void closeBridge(final TerminalBridge bridge) { 209 synchronized (flip) { 210 final int flipIndex = getFlipIndex(bridge); 211 212 if (flipIndex >= 0) { 213 if (flip.getDisplayedChild() == flipIndex) { 214 shiftCurrentTerminal(SHIFT_LEFT); 215 } 216 flip.removeViewAt(flipIndex); 217 218 /* 219 * TODO Remove this workaround when ViewFlipper is fixed to listen to view removals. Android 220 * Issue 1784 221 */ 222 final int numChildren = flip.getChildCount(); 223 if (flip.getDisplayedChild() >= numChildren && numChildren > 0) { 224 flip.setDisplayedChild(numChildren - 1); 225 } 226 } 227 228 // If we just closed the last bridge, go back to the previous activity. 229 if (flip.getChildCount() == 0) { 230 finish(); 231 } 232 } 233 } 234 findCurrentView(int id)235 protected View findCurrentView(int id) { 236 View view = flip.getCurrentView(); 237 if (view == null) { 238 return null; 239 } 240 return view.findViewById(id); 241 } 242 getCurrentPromptHelper()243 protected PromptHelper getCurrentPromptHelper() { 244 View view = findCurrentView(R.id.console_flip); 245 if (!(view instanceof TerminalView)) { 246 return null; 247 } 248 return ((TerminalView) view).bridge.getPromptHelper(); 249 } 250 hideAllPrompts()251 protected void hideAllPrompts() { 252 booleanPromptGroup.setVisibility(View.GONE); 253 } 254 255 @Override onCreate(Bundle icicle)256 public void onCreate(Bundle icicle) { 257 super.onCreate(icicle); 258 259 this.setContentView(R.layout.act_console); 260 261 clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 262 prefs = PreferenceManager.getDefaultSharedPreferences(this); 263 264 // hide status bar if requested by user 265 if (prefs.getBoolean(PreferenceConstants.FULLSCREEN, false)) { 266 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 267 WindowManager.LayoutParams.FLAG_FULLSCREEN); 268 } 269 270 // TODO find proper way to disable volume key beep if it exists. 271 setVolumeControlStream(AudioManager.STREAM_MUSIC); 272 273 PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE); 274 wakelock = manager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getPackageName()); 275 276 // handle requested console from incoming intent 277 int id = getIntent().getIntExtra(Constants.EXTRA_PROXY_PORT, -1); 278 279 if (id > 0) { 280 processID = id; 281 } 282 283 inflater = LayoutInflater.from(this); 284 285 flip = (ViewFlipper) findViewById(R.id.console_flip); 286 booleanPromptGroup = (RelativeLayout) findViewById(R.id.console_boolean_group); 287 booleanPrompt = (TextView) findViewById(R.id.console_prompt); 288 289 booleanYes = (Button) findViewById(R.id.console_prompt_yes); 290 booleanYes.setOnClickListener(new OnClickListener() { 291 public void onClick(View v) { 292 PromptHelper helper = getCurrentPromptHelper(); 293 if (helper == null) { 294 return; 295 } 296 helper.setResponse(Boolean.TRUE); 297 updatePromptVisible(); 298 } 299 }); 300 301 booleanNo = (Button) findViewById(R.id.console_prompt_no); 302 booleanNo.setOnClickListener(new OnClickListener() { 303 public void onClick(View v) { 304 PromptHelper helper = getCurrentPromptHelper(); 305 if (helper == null) { 306 return; 307 } 308 helper.setResponse(Boolean.FALSE); 309 updatePromptVisible(); 310 } 311 }); 312 313 // preload animations for terminal switching 314 slide_left_in = AnimationUtils.loadAnimation(this, R.anim.slide_left_in); 315 slide_left_out = AnimationUtils.loadAnimation(this, R.anim.slide_left_out); 316 slide_right_in = AnimationUtils.loadAnimation(this, R.anim.slide_right_in); 317 slide_right_out = AnimationUtils.loadAnimation(this, R.anim.slide_right_out); 318 319 fade_out_delayed = AnimationUtils.loadAnimation(this, R.anim.fade_out_delayed); 320 fade_stay_hidden = AnimationUtils.loadAnimation(this, R.anim.fade_stay_hidden); 321 322 // Preload animation for keyboard button 323 keyboard_fade_in = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_in); 324 keyboard_fade_out = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_out); 325 326 inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 327 keyboardButton = (ImageView) findViewById(R.id.keyboard_button); 328 keyboardButton.setOnClickListener(new OnClickListener() { 329 public void onClick(View view) { 330 View flip = findCurrentView(R.id.console_flip); 331 if (flip == null) { 332 return; 333 } 334 335 inputManager.showSoftInput(flip, InputMethodManager.SHOW_FORCED); 336 keyboardButton.setVisibility(View.GONE); 337 } 338 }); 339 if (prefs.getBoolean(PreferenceConstants.HIDE_KEYBOARD, false)) { 340 // Force hidden keyboard. 341 getWindow().setSoftInputMode( 342 WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN 343 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); 344 } 345 final ViewConfiguration configuration = ViewConfiguration.get(this); 346 int touchSlop = configuration.getScaledTouchSlop(); 347 mTouchSlopSquare = touchSlop * touchSlop; 348 349 // detect fling gestures to switch between terminals 350 final GestureDetector detect = 351 new GestureDetector(new GestureDetector.SimpleOnGestureListener() { 352 private float totalY = 0; 353 354 @Override 355 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 356 357 final float distx = e2.getRawX() - e1.getRawX(); 358 final float disty = e2.getRawY() - e1.getRawY(); 359 final int goalwidth = flip.getWidth() / 2; 360 361 // need to slide across half of display to trigger console change 362 // make sure user kept a steady hand horizontally 363 if (Math.abs(disty) < (flip.getHeight() / 4)) { 364 if (distx > goalwidth) { 365 shiftCurrentTerminal(SHIFT_RIGHT); 366 return true; 367 } 368 369 if (distx < -goalwidth) { 370 shiftCurrentTerminal(SHIFT_LEFT); 371 return true; 372 } 373 374 } 375 376 return false; 377 } 378 379 @Override 380 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 381 382 // if copying, then ignore 383 if (copySource != null && copySource.isSelectingForCopy()) { 384 return false; 385 } 386 387 if (e1 == null || e2 == null) { 388 return false; 389 } 390 391 // if releasing then reset total scroll 392 if (e2.getAction() == MotionEvent.ACTION_UP) { 393 totalY = 0; 394 } 395 396 // activate consider if within x tolerance 397 if (Math.abs(e1.getX() - e2.getX()) < ViewConfiguration.getTouchSlop() * 4) { 398 399 View flip = findCurrentView(R.id.console_flip); 400 if (flip == null) { 401 return false; 402 } 403 TerminalView terminal = (TerminalView) flip; 404 405 // estimate how many rows we have scrolled through 406 // accumulate distance that doesn't trigger immediate scroll 407 totalY += distanceY; 408 final int moved = (int) (totalY / terminal.bridge.charHeight); 409 410 VDUBuffer buffer = terminal.bridge.getVDUBuffer(); 411 412 // consume as scrollback only if towards right half of screen 413 if (e2.getX() > flip.getWidth() / 2) { 414 if (moved != 0) { 415 int base = buffer.getWindowBase(); 416 buffer.setWindowBase(base + moved); 417 totalY = 0; 418 return true; 419 } 420 } else { 421 // otherwise consume as pgup/pgdown for every 5 lines 422 if (moved > 5) { 423 ((vt320) buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0); 424 terminal.bridge.tryKeyVibrate(); 425 totalY = 0; 426 return true; 427 } else if (moved < -5) { 428 ((vt320) buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0); 429 terminal.bridge.tryKeyVibrate(); 430 totalY = 0; 431 return true; 432 } 433 434 } 435 436 } 437 438 return false; 439 } 440 441 }); 442 443 flip.setOnCreateContextMenuListener(this); 444 445 flip.setOnTouchListener(new OnTouchListener() { 446 447 public boolean onTouch(View v, MotionEvent event) { 448 449 // when copying, highlight the area 450 if (copySource != null && copySource.isSelectingForCopy()) { 451 int row = (int) Math.floor(event.getY() / copySource.charHeight); 452 int col = (int) Math.floor(event.getX() / copySource.charWidth); 453 454 SelectionArea area = copySource.getSelectionArea(); 455 456 switch (event.getAction()) { 457 case MotionEvent.ACTION_DOWN: 458 // recording starting area 459 if (area.isSelectingOrigin()) { 460 area.setRow(row); 461 area.setColumn(col); 462 lastTouchRow = row; 463 lastTouchCol = col; 464 copySource.redraw(); 465 } 466 return true; 467 case MotionEvent.ACTION_MOVE: 468 /* 469 * ignore when user hasn't moved since last time so we can fine-tune with directional 470 * pad 471 */ 472 if (row == lastTouchRow && col == lastTouchCol) { 473 return true; 474 } 475 // if the user moves, start the selection for other corner 476 area.finishSelectingOrigin(); 477 478 // update selected area 479 area.setRow(row); 480 area.setColumn(col); 481 lastTouchRow = row; 482 lastTouchCol = col; 483 copySource.redraw(); 484 return true; 485 case MotionEvent.ACTION_UP: 486 /* 487 * If they didn't move their finger, maybe they meant to select the rest of the text 488 * with the directional pad. 489 */ 490 if (area.getLeft() == area.getRight() && area.getTop() == area.getBottom()) { 491 return true; 492 } 493 494 // copy selected area to clipboard 495 String copiedText = area.copyFrom(copySource.getVDUBuffer()); 496 497 clipboard.setText(copiedText); 498 Toast.makeText(ConsoleActivity.this, 499 getString(R.string.terminal_copy_done, copiedText.length()), Toast.LENGTH_LONG) 500 .show(); 501 // fall through to clear state 502 503 case MotionEvent.ACTION_CANCEL: 504 // make sure we clear any highlighted area 505 area.reset(); 506 copySource.setSelectingForCopy(false); 507 copySource.redraw(); 508 return true; 509 } 510 } 511 512 Configuration config = getResources().getConfiguration(); 513 514 if (event.getAction() == MotionEvent.ACTION_DOWN) { 515 lastX = event.getX(); 516 lastY = event.getY(); 517 } else if (event.getAction() == MotionEvent.ACTION_MOVE) { 518 final int deltaX = (int) (lastX - event.getX()); 519 final int deltaY = (int) (lastY - event.getY()); 520 int distance = (deltaX * deltaX) + (deltaY * deltaY); 521 if (distance > mTouchSlopSquare) { 522 // If currently scheduled long press event is not canceled here, 523 // GestureDetector.onScroll is executed, which takes a while, and by the time we are 524 // back in the view's dispatchTouchEvent 525 // mPendingCheckForLongPress is already executed 526 flip.cancelLongPress(); 527 } 528 } else if (event.getAction() == MotionEvent.ACTION_UP) { 529 // Same as above, except now GestureDetector.onFling is called. 530 flip.cancelLongPress(); 531 if (config.hardKeyboardHidden != Configuration.KEYBOARDHIDDEN_NO 532 && keyboardButton.getVisibility() == View.GONE 533 && event.getEventTime() - event.getDownTime() < CLICK_TIME 534 && Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE 535 && Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) { 536 keyboardButton.startAnimation(keyboard_fade_in); 537 keyboardButton.setVisibility(View.VISIBLE); 538 539 handler.postDelayed(new Runnable() { 540 public void run() { 541 if (keyboardButton.getVisibility() == View.GONE) { 542 return; 543 } 544 545 keyboardButton.startAnimation(keyboard_fade_out); 546 keyboardButton.setVisibility(View.GONE); 547 } 548 }, KEYBOARD_DISPLAY_TIME); 549 550 return false; 551 } 552 } 553 // pass any touch events back to detector 554 return detect.onTouchEvent(event); 555 } 556 557 }); 558 559 } 560 configureOrientation()561 private void configureOrientation() { 562 String rotateDefault; 563 if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS) { 564 rotateDefault = PreferenceConstants.ROTATION_PORTRAIT; 565 } else { 566 rotateDefault = PreferenceConstants.ROTATION_LANDSCAPE; 567 } 568 569 String rotate = prefs.getString(PreferenceConstants.ROTATION, rotateDefault); 570 if (PreferenceConstants.ROTATION_DEFAULT.equals(rotate)) { 571 rotate = rotateDefault; 572 } 573 574 // request a forced orientation if requested by user 575 if (PreferenceConstants.ROTATION_LANDSCAPE.equals(rotate)) { 576 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 577 forcedOrientation = true; 578 } else if (PreferenceConstants.ROTATION_PORTRAIT.equals(rotate)) { 579 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 580 forcedOrientation = true; 581 } else { 582 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 583 forcedOrientation = false; 584 } 585 } 586 587 @Override onCreateOptionsMenu(Menu menu)588 public boolean onCreateOptionsMenu(Menu menu) { 589 super.onCreateOptionsMenu(menu); 590 getMenuInflater().inflate(R.menu.terminal, menu); 591 menu.setQwertyMode(true); 592 return true; 593 } 594 595 @Override onPrepareOptionsMenu(Menu menu)596 public boolean onPrepareOptionsMenu(Menu menu) { 597 super.onPrepareOptionsMenu(menu); 598 setVolumeControlStream(AudioManager.STREAM_NOTIFICATION); 599 TerminalBridge bridge = ((TerminalView) findCurrentView(R.id.console_flip)).bridge; 600 boolean sessionOpen = bridge.isSessionOpen(); 601 menu.findItem(R.id.terminal_menu_resize).setEnabled(sessionOpen); 602 if (bridge.getProcess() instanceof ScriptProcess) { 603 menu.findItem(R.id.terminal_menu_exit_and_edit).setEnabled(true); 604 } 605 bridge.onPrepareOptionsMenu(menu); 606 return true; 607 } 608 609 @Override onOptionsItemSelected(MenuItem item)610 public boolean onOptionsItemSelected(MenuItem item) { 611 if (item.getItemId() == R.id.terminal_menu_resize) { 612 doResize(); 613 } else if (item.getItemId() == R.id.terminal_menu_preferences) { 614 doPreferences(); 615 } else if (item.getItemId() == R.id.terminal_menu_send_email) { 616 doEmailTranscript(); 617 } else if (item.getItemId() == R.id.terminal_menu_exit_and_edit) { 618 TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); 619 TerminalBridge bridge = terminalView.bridge; 620 if (manager != null) { 621 manager.closeConnection(bridge, true); 622 } else { 623 Intent intent = new Intent(this, ScriptingLayerService.class); 624 intent.setAction(Constants.ACTION_KILL_PROCESS); 625 intent.putExtra(Constants.EXTRA_PROXY_PORT, bridge.getId()); 626 Log.i(String.format("Killing process from ConsoleActivity, %s", intent.toUri(0))); 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 onOptionsMenuClosed(Menu menu)640 public void onOptionsMenuClosed(Menu menu) { 641 super.onOptionsMenuClosed(menu); 642 setVolumeControlStream(AudioManager.STREAM_MUSIC); 643 } 644 doResize()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 doPreferences()668 private void doPreferences() { 669 startActivity(new Intent(this, Preferences.class)); 670 } 671 doEmailTranscript()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@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 onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)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 onContextItemSelected(MenuItem item)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 onStart()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 onPause()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 onResume()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 onNewIntent(Intent intent)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 onStop()826 public void onStop() { 827 super.onStop(); 828 unbindService(mConnection); 829 } 830 shiftCurrentTerminal(final int direction)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 */ updatePromptVisible()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 onConfigurationChanged(Configuration newConfig)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 */ addNewTerminalView(TerminalBridge bridge)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 getFlipIndex(TerminalBridge bridge)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 */ setDisplayedTerminal(int requestedIndex)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