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; getId()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 */ closeBridge(final TerminalBridge bridge)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 findCurrentView(int id)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 getCurrentPromptHelper()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 hideAllPrompts()252 protected void hideAllPrompts() { 253 booleanPromptGroup.setVisibility(View.GONE); 254 } 255 256 @Override onCreate(Bundle icicle)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 configureOrientation()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 onCreateOptionsMenu(Menu menu)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 onPrepareOptionsMenu(Menu menu)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 onOptionsItemSelected(MenuItem item)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 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