| /* |
| * ConnectBot: simple, powerful, open-source SSH client for Android |
| * Copyright 2007 Kenny Root, Jeffrey Sharkey |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @author modified by raaar |
| * |
| */ |
| |
| package org.connectbot; |
| |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.content.SharedPreferences; |
| import android.content.pm.ActivityInfo; |
| import android.content.res.Configuration; |
| import android.media.AudioManager; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.PowerManager; |
| import android.preference.PreferenceManager; |
| import android.text.ClipboardManager; |
| import android.view.ContextMenu; |
| import android.view.ContextMenu.ContextMenuInfo; |
| import android.view.GestureDetector; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.View.OnTouchListener; |
| import android.view.ViewConfiguration; |
| import android.view.WindowManager; |
| import android.view.animation.Animation; |
| import android.view.animation.AnimationUtils; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.Button; |
| import android.widget.EditText; |
| import android.widget.ImageView; |
| import android.widget.RelativeLayout; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| import android.widget.ViewFlipper; |
| |
| import com.googlecode.android_scripting.Constants; |
| import com.googlecode.android_scripting.Log; |
| import com.googlecode.android_scripting.R; |
| import com.googlecode.android_scripting.ScriptProcess; |
| import com.googlecode.android_scripting.activity.Preferences; |
| import com.googlecode.android_scripting.activity.ScriptingLayerService; |
| |
| import de.mud.terminal.VDUBuffer; |
| import de.mud.terminal.vt320; |
| |
| import org.connectbot.service.PromptHelper; |
| import org.connectbot.service.TerminalBridge; |
| import org.connectbot.service.TerminalManager; |
| import org.connectbot.util.PreferenceConstants; |
| import org.connectbot.util.SelectionArea; |
| |
| public class ConsoleActivity extends Activity { |
| |
| protected static final int REQUEST_EDIT = 1; |
| |
| private static final int CLICK_TIME = 250; |
| private static final float MAX_CLICK_DISTANCE = 25f; |
| private static final int KEYBOARD_DISPLAY_TIME = 1250; |
| |
| // Direction to shift the ViewFlipper |
| private static final int SHIFT_LEFT = 0; |
| private static final int SHIFT_RIGHT = 1; |
| |
| protected ViewFlipper flip = null; |
| protected TerminalManager manager = null; |
| protected ScriptingLayerService mService = null; |
| protected LayoutInflater inflater = null; |
| |
| private SharedPreferences prefs = null; |
| |
| private PowerManager.WakeLock wakelock = null; |
| |
| protected Integer processID; |
| |
| protected ClipboardManager clipboard; |
| |
| private RelativeLayout booleanPromptGroup; |
| private TextView booleanPrompt; |
| private Button booleanYes, booleanNo; |
| |
| private Animation slide_left_in, slide_left_out, slide_right_in, slide_right_out, |
| fade_stay_hidden, fade_out_delayed; |
| |
| private Animation keyboard_fade_in, keyboard_fade_out; |
| private ImageView keyboardButton; |
| private float lastX, lastY; |
| |
| private int mTouchSlopSquare; |
| |
| private InputMethodManager inputManager; |
| |
| protected TerminalBridge copySource = null; |
| private int lastTouchRow, lastTouchCol; |
| |
| private boolean forcedOrientation; |
| |
| private Handler handler = new Handler(); |
| |
| private static enum MenuId { |
| EDIT, PREFS, EMAIL, RESIZE, COPY, PASTE; |
| public int getId() { |
| return ordinal() + Menu.FIRST; |
| } |
| } |
| |
| private final ServiceConnection mConnection = new ServiceConnection() { |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| mService = ((ScriptingLayerService.LocalBinder) service).getService(); |
| manager = mService.getTerminalManager(); |
| // let manager know about our event handling services |
| manager.setDisconnectHandler(disconnectHandler); |
| |
| Log.d(String.format("Connected to TerminalManager and found bridges.size=%d", manager |
| .getBridgeList().size())); |
| |
| manager.setResizeAllowed(true); |
| |
| // clear out any existing bridges and record requested index |
| flip.removeAllViews(); |
| |
| int requestedIndex = 0; |
| |
| TerminalBridge requestedBridge = manager.getConnectedBridge(processID); |
| |
| // If we didn't find the requested connection, try opening it |
| if (processID != null && requestedBridge == null) { |
| try { |
| Log.d(String.format( |
| "We couldnt find an existing bridge with id = %d, so creating one now", processID)); |
| requestedBridge = manager.openConnection(processID); |
| } catch (Exception e) { |
| Log.e("Problem while trying to create new requested bridge", e); |
| } |
| } |
| |
| // create views for all bridges on this service |
| for (TerminalBridge bridge : manager.getBridgeList()) { |
| |
| final int currentIndex = addNewTerminalView(bridge); |
| |
| // check to see if this bridge was requested |
| if (bridge == requestedBridge) { |
| requestedIndex = currentIndex; |
| } |
| } |
| |
| setDisplayedTerminal(requestedIndex); |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| manager = null; |
| mService = null; |
| } |
| }; |
| |
| protected Handler promptHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| // someone below us requested to display a prompt |
| updatePromptVisible(); |
| } |
| }; |
| |
| protected Handler disconnectHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| Log.d("Someone sending HANDLE_DISCONNECT to parentHandler"); |
| TerminalBridge bridge = (TerminalBridge) msg.obj; |
| closeBridge(bridge); |
| } |
| }; |
| |
| /** |
| * @param bridge |
| */ |
| private void closeBridge(final TerminalBridge bridge) { |
| synchronized (flip) { |
| final int flipIndex = getFlipIndex(bridge); |
| |
| if (flipIndex >= 0) { |
| if (flip.getDisplayedChild() == flipIndex) { |
| shiftCurrentTerminal(SHIFT_LEFT); |
| } |
| flip.removeViewAt(flipIndex); |
| |
| /* |
| * TODO Remove this workaround when ViewFlipper is fixed to listen to view removals. Android |
| * Issue 1784 |
| */ |
| final int numChildren = flip.getChildCount(); |
| if (flip.getDisplayedChild() >= numChildren && numChildren > 0) { |
| flip.setDisplayedChild(numChildren - 1); |
| } |
| } |
| |
| // If we just closed the last bridge, go back to the previous activity. |
| if (flip.getChildCount() == 0) { |
| finish(); |
| } |
| } |
| } |
| |
| protected View findCurrentView(int id) { |
| View view = flip.getCurrentView(); |
| if (view == null) { |
| return null; |
| } |
| return view.findViewById(id); |
| } |
| |
| protected PromptHelper getCurrentPromptHelper() { |
| View view = findCurrentView(R.id.console_flip); |
| if (!(view instanceof TerminalView)) { |
| return null; |
| } |
| return ((TerminalView) view).bridge.getPromptHelper(); |
| } |
| |
| protected void hideAllPrompts() { |
| booleanPromptGroup.setVisibility(View.GONE); |
| } |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| this.setContentView(R.layout.act_console); |
| |
| clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); |
| prefs = PreferenceManager.getDefaultSharedPreferences(this); |
| |
| // hide status bar if requested by user |
| if (prefs.getBoolean(PreferenceConstants.FULLSCREEN, false)) { |
| getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, |
| WindowManager.LayoutParams.FLAG_FULLSCREEN); |
| } |
| |
| // TODO find proper way to disable volume key beep if it exists. |
| setVolumeControlStream(AudioManager.STREAM_MUSIC); |
| |
| PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE); |
| wakelock = manager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getPackageName()); |
| |
| // handle requested console from incoming intent |
| int id = getIntent().getIntExtra(Constants.EXTRA_PROXY_PORT, -1); |
| |
| if (id > 0) { |
| processID = id; |
| } |
| |
| inflater = LayoutInflater.from(this); |
| |
| flip = (ViewFlipper) findViewById(R.id.console_flip); |
| booleanPromptGroup = (RelativeLayout) findViewById(R.id.console_boolean_group); |
| booleanPrompt = (TextView) findViewById(R.id.console_prompt); |
| |
| booleanYes = (Button) findViewById(R.id.console_prompt_yes); |
| booleanYes.setOnClickListener(new OnClickListener() { |
| public void onClick(View v) { |
| PromptHelper helper = getCurrentPromptHelper(); |
| if (helper == null) { |
| return; |
| } |
| helper.setResponse(Boolean.TRUE); |
| updatePromptVisible(); |
| } |
| }); |
| |
| booleanNo = (Button) findViewById(R.id.console_prompt_no); |
| booleanNo.setOnClickListener(new OnClickListener() { |
| public void onClick(View v) { |
| PromptHelper helper = getCurrentPromptHelper(); |
| if (helper == null) { |
| return; |
| } |
| helper.setResponse(Boolean.FALSE); |
| updatePromptVisible(); |
| } |
| }); |
| |
| // preload animations for terminal switching |
| slide_left_in = AnimationUtils.loadAnimation(this, R.anim.slide_left_in); |
| slide_left_out = AnimationUtils.loadAnimation(this, R.anim.slide_left_out); |
| slide_right_in = AnimationUtils.loadAnimation(this, R.anim.slide_right_in); |
| slide_right_out = AnimationUtils.loadAnimation(this, R.anim.slide_right_out); |
| |
| fade_out_delayed = AnimationUtils.loadAnimation(this, R.anim.fade_out_delayed); |
| fade_stay_hidden = AnimationUtils.loadAnimation(this, R.anim.fade_stay_hidden); |
| |
| // Preload animation for keyboard button |
| keyboard_fade_in = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_in); |
| keyboard_fade_out = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_out); |
| |
| inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); |
| keyboardButton = (ImageView) findViewById(R.id.keyboard_button); |
| keyboardButton.setOnClickListener(new OnClickListener() { |
| public void onClick(View view) { |
| View flip = findCurrentView(R.id.console_flip); |
| if (flip == null) { |
| return; |
| } |
| |
| inputManager.showSoftInput(flip, InputMethodManager.SHOW_FORCED); |
| keyboardButton.setVisibility(View.GONE); |
| } |
| }); |
| if (prefs.getBoolean(PreferenceConstants.HIDE_KEYBOARD, false)) { |
| // Force hidden keyboard. |
| getWindow().setSoftInputMode( |
| WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN |
| | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); |
| } |
| final ViewConfiguration configuration = ViewConfiguration.get(this); |
| int touchSlop = configuration.getScaledTouchSlop(); |
| mTouchSlopSquare = touchSlop * touchSlop; |
| |
| // detect fling gestures to switch between terminals |
| final GestureDetector detect = |
| new GestureDetector(new GestureDetector.SimpleOnGestureListener() { |
| private float totalY = 0; |
| |
| @Override |
| public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { |
| |
| final float distx = e2.getRawX() - e1.getRawX(); |
| final float disty = e2.getRawY() - e1.getRawY(); |
| final int goalwidth = flip.getWidth() / 2; |
| |
| // need to slide across half of display to trigger console change |
| // make sure user kept a steady hand horizontally |
| if (Math.abs(disty) < (flip.getHeight() / 4)) { |
| if (distx > goalwidth) { |
| shiftCurrentTerminal(SHIFT_RIGHT); |
| return true; |
| } |
| |
| if (distx < -goalwidth) { |
| shiftCurrentTerminal(SHIFT_LEFT); |
| return true; |
| } |
| |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { |
| |
| // if copying, then ignore |
| if (copySource != null && copySource.isSelectingForCopy()) { |
| return false; |
| } |
| |
| if (e1 == null || e2 == null) { |
| return false; |
| } |
| |
| // if releasing then reset total scroll |
| if (e2.getAction() == MotionEvent.ACTION_UP) { |
| totalY = 0; |
| } |
| |
| // activate consider if within x tolerance |
| if (Math.abs(e1.getX() - e2.getX()) < ViewConfiguration.getTouchSlop() * 4) { |
| |
| View flip = findCurrentView(R.id.console_flip); |
| if (flip == null) { |
| return false; |
| } |
| TerminalView terminal = (TerminalView) flip; |
| |
| // estimate how many rows we have scrolled through |
| // accumulate distance that doesn't trigger immediate scroll |
| totalY += distanceY; |
| final int moved = (int) (totalY / terminal.bridge.charHeight); |
| |
| VDUBuffer buffer = terminal.bridge.getVDUBuffer(); |
| |
| // consume as scrollback only if towards right half of screen |
| if (e2.getX() > flip.getWidth() / 2) { |
| if (moved != 0) { |
| int base = buffer.getWindowBase(); |
| buffer.setWindowBase(base + moved); |
| totalY = 0; |
| return true; |
| } |
| } else { |
| // otherwise consume as pgup/pgdown for every 5 lines |
| if (moved > 5) { |
| ((vt320) buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0); |
| terminal.bridge.tryKeyVibrate(); |
| totalY = 0; |
| return true; |
| } else if (moved < -5) { |
| ((vt320) buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0); |
| terminal.bridge.tryKeyVibrate(); |
| totalY = 0; |
| return true; |
| } |
| |
| } |
| |
| } |
| |
| return false; |
| } |
| |
| }); |
| |
| flip.setOnCreateContextMenuListener(this); |
| |
| flip.setOnTouchListener(new OnTouchListener() { |
| |
| public boolean onTouch(View v, MotionEvent event) { |
| |
| // when copying, highlight the area |
| if (copySource != null && copySource.isSelectingForCopy()) { |
| int row = (int) Math.floor(event.getY() / copySource.charHeight); |
| int col = (int) Math.floor(event.getX() / copySource.charWidth); |
| |
| SelectionArea area = copySource.getSelectionArea(); |
| |
| switch (event.getAction()) { |
| case MotionEvent.ACTION_DOWN: |
| // recording starting area |
| if (area.isSelectingOrigin()) { |
| area.setRow(row); |
| area.setColumn(col); |
| lastTouchRow = row; |
| lastTouchCol = col; |
| copySource.redraw(); |
| } |
| return true; |
| case MotionEvent.ACTION_MOVE: |
| /* |
| * ignore when user hasn't moved since last time so we can fine-tune with directional |
| * pad |
| */ |
| if (row == lastTouchRow && col == lastTouchCol) { |
| return true; |
| } |
| // if the user moves, start the selection for other corner |
| area.finishSelectingOrigin(); |
| |
| // update selected area |
| area.setRow(row); |
| area.setColumn(col); |
| lastTouchRow = row; |
| lastTouchCol = col; |
| copySource.redraw(); |
| return true; |
| case MotionEvent.ACTION_UP: |
| /* |
| * If they didn't move their finger, maybe they meant to select the rest of the text |
| * with the directional pad. |
| */ |
| if (area.getLeft() == area.getRight() && area.getTop() == area.getBottom()) { |
| return true; |
| } |
| |
| // copy selected area to clipboard |
| String copiedText = area.copyFrom(copySource.getVDUBuffer()); |
| |
| clipboard.setText(copiedText); |
| Toast.makeText(ConsoleActivity.this, |
| getString(R.string.terminal_copy_done, copiedText.length()), Toast.LENGTH_LONG) |
| .show(); |
| // fall through to clear state |
| |
| case MotionEvent.ACTION_CANCEL: |
| // make sure we clear any highlighted area |
| area.reset(); |
| copySource.setSelectingForCopy(false); |
| copySource.redraw(); |
| return true; |
| } |
| } |
| |
| Configuration config = getResources().getConfiguration(); |
| |
| if (event.getAction() == MotionEvent.ACTION_DOWN) { |
| lastX = event.getX(); |
| lastY = event.getY(); |
| } else if (event.getAction() == MotionEvent.ACTION_MOVE) { |
| final int deltaX = (int) (lastX - event.getX()); |
| final int deltaY = (int) (lastY - event.getY()); |
| int distance = (deltaX * deltaX) + (deltaY * deltaY); |
| if (distance > mTouchSlopSquare) { |
| // If currently scheduled long press event is not canceled here, |
| // GestureDetector.onScroll is executed, which takes a while, and by the time we are |
| // back in the view's dispatchTouchEvent |
| // mPendingCheckForLongPress is already executed |
| flip.cancelLongPress(); |
| } |
| } else if (event.getAction() == MotionEvent.ACTION_UP) { |
| // Same as above, except now GestureDetector.onFling is called. |
| flip.cancelLongPress(); |
| if (config.hardKeyboardHidden != Configuration.KEYBOARDHIDDEN_NO |
| && keyboardButton.getVisibility() == View.GONE |
| && event.getEventTime() - event.getDownTime() < CLICK_TIME |
| && Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE |
| && Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) { |
| keyboardButton.startAnimation(keyboard_fade_in); |
| keyboardButton.setVisibility(View.VISIBLE); |
| |
| handler.postDelayed(new Runnable() { |
| public void run() { |
| if (keyboardButton.getVisibility() == View.GONE) { |
| return; |
| } |
| |
| keyboardButton.startAnimation(keyboard_fade_out); |
| keyboardButton.setVisibility(View.GONE); |
| } |
| }, KEYBOARD_DISPLAY_TIME); |
| |
| return false; |
| } |
| } |
| // pass any touch events back to detector |
| return detect.onTouchEvent(event); |
| } |
| |
| }); |
| |
| } |
| |
| private void configureOrientation() { |
| String rotateDefault; |
| if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS) { |
| rotateDefault = PreferenceConstants.ROTATION_PORTRAIT; |
| } else { |
| rotateDefault = PreferenceConstants.ROTATION_LANDSCAPE; |
| } |
| |
| String rotate = prefs.getString(PreferenceConstants.ROTATION, rotateDefault); |
| if (PreferenceConstants.ROTATION_DEFAULT.equals(rotate)) { |
| rotate = rotateDefault; |
| } |
| |
| // request a forced orientation if requested by user |
| if (PreferenceConstants.ROTATION_LANDSCAPE.equals(rotate)) { |
| setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
| forcedOrientation = true; |
| } else if (PreferenceConstants.ROTATION_PORTRAIT.equals(rotate)) { |
| setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
| forcedOrientation = true; |
| } else { |
| setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); |
| forcedOrientation = false; |
| } |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| super.onCreateOptionsMenu(menu); |
| getMenuInflater().inflate(R.menu.terminal, menu); |
| menu.setQwertyMode(true); |
| return true; |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| super.onPrepareOptionsMenu(menu); |
| setVolumeControlStream(AudioManager.STREAM_NOTIFICATION); |
| TerminalBridge bridge = ((TerminalView) findCurrentView(R.id.console_flip)).bridge; |
| boolean sessionOpen = bridge.isSessionOpen(); |
| menu.findItem(R.id.terminal_menu_resize).setEnabled(sessionOpen); |
| if (bridge.getProcess() instanceof ScriptProcess) { |
| menu.findItem(R.id.terminal_menu_exit_and_edit).setEnabled(true); |
| } |
| bridge.onPrepareOptionsMenu(menu); |
| return true; |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| if (item.getItemId() == R.id.terminal_menu_resize) { |
| doResize(); |
| } else if (item.getItemId() == R.id.terminal_menu_preferences) { |
| doPreferences(); |
| } else if (item.getItemId() == R.id.terminal_menu_send_email) { |
| doEmailTranscript(); |
| } else if (item.getItemId() == R.id.terminal_menu_exit_and_edit) { |
| TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); |
| TerminalBridge bridge = terminalView.bridge; |
| if (manager != null) { |
| manager.closeConnection(bridge, true); |
| } else { |
| Intent intent = new Intent(this, ScriptingLayerService.class); |
| intent.setAction(Constants.ACTION_KILL_PROCESS); |
| intent.putExtra(Constants.EXTRA_PROXY_PORT, bridge.getId()); |
| startService(intent); |
| Message.obtain(disconnectHandler, -1, bridge).sendToTarget(); |
| } |
| Intent intent = new Intent(Constants.ACTION_EDIT_SCRIPT); |
| ScriptProcess process = (ScriptProcess) bridge.getProcess(); |
| intent.putExtra(Constants.EXTRA_SCRIPT_PATH, process.getPath()); |
| startActivity(intent); |
| finish(); |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| @Override |
| public void onOptionsMenuClosed(Menu menu) { |
| super.onOptionsMenuClosed(menu); |
| setVolumeControlStream(AudioManager.STREAM_MUSIC); |
| } |
| |
| private void doResize() { |
| closeOptionsMenu(); |
| final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); |
| final View resizeView = inflater.inflate(R.layout.dia_resize, null, false); |
| new AlertDialog.Builder(ConsoleActivity.this).setView(resizeView) |
| .setPositiveButton(R.string.button_resize, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| int width, height; |
| try { |
| width = |
| Integer.parseInt(((EditText) resizeView.findViewById(R.id.width)).getText() |
| .toString()); |
| height = |
| Integer.parseInt(((EditText) resizeView.findViewById(R.id.height)).getText() |
| .toString()); |
| } catch (NumberFormatException nfe) { |
| return; |
| } |
| terminalView.forceSize(width, height); |
| } |
| }).setNegativeButton(android.R.string.cancel, null).create().show(); |
| } |
| |
| private void doPreferences() { |
| startActivity(new Intent(this, Preferences.class)); |
| } |
| |
| private void doEmailTranscript() { |
| // Don't really want to supply an address, but currently it's required, |
| // otherwise we get an exception. |
| TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); |
| TerminalBridge bridge = terminalView.bridge; |
| // TODO(raaar): Replace with process log. |
| VDUBuffer buffer = bridge.getVDUBuffer(); |
| int height = buffer.getRows(); |
| int width = buffer.getColumns(); |
| StringBuilder string = new StringBuilder(); |
| for (int i = 0; i < height; i++) { |
| for (int j = 0; j < width; j++) { |
| string.append(buffer.getChar(j, i)); |
| } |
| } |
| String addr = "user@example.com"; |
| Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + addr)); |
| intent.putExtra("body", string.toString().trim()); |
| startActivity(intent); |
| } |
| |
| @Override |
| public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { |
| TerminalBridge bridge = ((TerminalView) findCurrentView(R.id.console_flip)).bridge; |
| boolean sessionOpen = bridge.isSessionOpen(); |
| menu.add(Menu.NONE, MenuId.COPY.getId(), Menu.NONE, R.string.terminal_menu_copy); |
| if (clipboard.hasText() && sessionOpen) { |
| menu.add(Menu.NONE, MenuId.PASTE.getId(), Menu.NONE, R.string.terminal_menu_paste); |
| } |
| bridge.onCreateContextMenu(menu, view, menuInfo); |
| } |
| |
| @Override |
| public boolean onContextItemSelected(MenuItem item) { |
| int itemId = item.getItemId(); |
| if (itemId == MenuId.COPY.getId()) { |
| TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); |
| copySource = terminalView.bridge; |
| SelectionArea area = copySource.getSelectionArea(); |
| area.reset(); |
| area.setBounds(copySource.getVDUBuffer().getColumns(), copySource.getVDUBuffer().getRows()); |
| copySource.setSelectingForCopy(true); |
| // Make sure we show the initial selection |
| copySource.redraw(); |
| Toast.makeText(ConsoleActivity.this, getString(R.string.terminal_copy_start), |
| Toast.LENGTH_LONG).show(); |
| return true; |
| } else if (itemId == MenuId.PASTE.getId()) { |
| TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); |
| TerminalBridge bridge = terminalView.bridge; |
| // pull string from clipboard and generate all events to force down |
| String clip = clipboard.getText().toString(); |
| bridge.injectString(clip); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| // connect with manager service to find all bridges |
| // when connected it will insert all views |
| bindService(new Intent(this, ScriptingLayerService.class), mConnection, 0); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| Log.d("onPause called"); |
| |
| // Allow the screen to dim and fall asleep. |
| if (wakelock != null && wakelock.isHeld()) { |
| wakelock.release(); |
| } |
| |
| if (forcedOrientation && manager != null) { |
| manager.setResizeAllowed(false); |
| } |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| Log.d("onResume called"); |
| |
| // Make sure we don't let the screen fall asleep. |
| // This also keeps the Wi-Fi chipset from disconnecting us. |
| if (wakelock != null && prefs.getBoolean(PreferenceConstants.KEEP_ALIVE, true)) { |
| wakelock.acquire(); |
| } |
| |
| configureOrientation(); |
| |
| if (forcedOrientation && manager != null) { |
| manager.setResizeAllowed(true); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see android.app.Activity#onNewIntent(android.content.Intent) |
| */ |
| @Override |
| protected void onNewIntent(Intent intent) { |
| super.onNewIntent(intent); |
| |
| Log.d("onNewIntent called"); |
| |
| int id = intent.getIntExtra(Constants.EXTRA_PROXY_PORT, -1); |
| |
| if (id > 0) { |
| processID = id; |
| } |
| |
| if (processID == null) { |
| Log.e("Got null intent data in onNewIntent()"); |
| return; |
| } |
| |
| if (manager == null) { |
| Log.e("We're not bound in onNewIntent()"); |
| return; |
| } |
| |
| TerminalBridge requestedBridge = manager.getConnectedBridge(processID); |
| int requestedIndex = 0; |
| |
| synchronized (flip) { |
| if (requestedBridge == null) { |
| // If we didn't find the requested connection, try opening it |
| |
| try { |
| Log.d(String.format("We couldnt find an existing bridge with id = %d," |
| + "so creating one now", processID)); |
| requestedBridge = manager.openConnection(processID); |
| } catch (Exception e) { |
| Log.e("Problem while trying to create new requested bridge", e); |
| } |
| |
| requestedIndex = addNewTerminalView(requestedBridge); |
| } else { |
| final int flipIndex = getFlipIndex(requestedBridge); |
| if (flipIndex > requestedIndex) { |
| requestedIndex = flipIndex; |
| } |
| } |
| |
| setDisplayedTerminal(requestedIndex); |
| } |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| unbindService(mConnection); |
| } |
| |
| protected void shiftCurrentTerminal(final int direction) { |
| View overlay; |
| synchronized (flip) { |
| boolean shouldAnimate = flip.getChildCount() > 1; |
| |
| // Only show animation if there is something else to go to. |
| if (shouldAnimate) { |
| // keep current overlay from popping up again |
| overlay = findCurrentView(R.id.terminal_overlay); |
| if (overlay != null) { |
| overlay.startAnimation(fade_stay_hidden); |
| } |
| |
| if (direction == SHIFT_LEFT) { |
| flip.setInAnimation(slide_left_in); |
| flip.setOutAnimation(slide_left_out); |
| flip.showNext(); |
| } else if (direction == SHIFT_RIGHT) { |
| flip.setInAnimation(slide_right_in); |
| flip.setOutAnimation(slide_right_out); |
| flip.showPrevious(); |
| } |
| } |
| |
| if (shouldAnimate) { |
| // show overlay on new slide and start fade |
| overlay = findCurrentView(R.id.terminal_overlay); |
| if (overlay != null) { |
| overlay.startAnimation(fade_out_delayed); |
| } |
| } |
| |
| updatePromptVisible(); |
| } |
| } |
| |
| /** |
| * Show any prompts requested by the currently visible {@link TerminalView}. |
| */ |
| protected void updatePromptVisible() { |
| // check if our currently-visible terminalbridge is requesting any prompt services |
| View view = findCurrentView(R.id.console_flip); |
| |
| // Hide all the prompts in case a prompt request was canceled |
| hideAllPrompts(); |
| |
| if (!(view instanceof TerminalView)) { |
| // we dont have an active view, so hide any prompts |
| return; |
| } |
| |
| PromptHelper prompt = ((TerminalView) view).bridge.getPromptHelper(); |
| |
| if (Boolean.class.equals(prompt.promptRequested)) { |
| booleanPromptGroup.setVisibility(View.VISIBLE); |
| booleanPrompt.setText(prompt.promptHint); |
| booleanYes.requestFocus(); |
| } else { |
| hideAllPrompts(); |
| view.requestFocus(); |
| } |
| } |
| |
| @Override |
| public void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| |
| Log.d(String.format( |
| "onConfigurationChanged; requestedOrientation=%d, newConfig.orientation=%d", |
| getRequestedOrientation(), newConfig.orientation)); |
| if (manager != null) { |
| if (forcedOrientation |
| && (newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) |
| || (newConfig.orientation != Configuration.ORIENTATION_PORTRAIT && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)) { |
| manager.setResizeAllowed(false); |
| } else { |
| manager.setResizeAllowed(true); |
| } |
| |
| manager |
| .setHardKeyboardHidden(newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES); |
| } |
| } |
| |
| /** |
| * Adds a new TerminalBridge to the current set of views in our ViewFlipper. |
| * |
| * @param bridge |
| * TerminalBridge to add to our ViewFlipper |
| * @return the child index of the new view in the ViewFlipper |
| */ |
| private int addNewTerminalView(TerminalBridge bridge) { |
| // let them know about our prompt handler services |
| bridge.getPromptHelper().setHandler(promptHandler); |
| |
| // inflate each terminal view |
| RelativeLayout view = (RelativeLayout) inflater.inflate(R.layout.item_terminal, flip, false); |
| |
| // set the terminal overlay text |
| TextView overlay = (TextView) view.findViewById(R.id.terminal_overlay); |
| overlay.setText(bridge.getName()); |
| |
| // and add our terminal view control, using index to place behind overlay |
| TerminalView terminal = new TerminalView(ConsoleActivity.this, bridge); |
| terminal.setId(R.id.console_flip); |
| view.addView(terminal, 0); |
| |
| synchronized (flip) { |
| // finally attach to the flipper |
| flip.addView(view); |
| return flip.getChildCount() - 1; |
| } |
| } |
| |
| private int getFlipIndex(TerminalBridge bridge) { |
| synchronized (flip) { |
| final int children = flip.getChildCount(); |
| for (int i = 0; i < children; i++) { |
| final View view = flip.getChildAt(i).findViewById(R.id.console_flip); |
| |
| if (view == null || !(view instanceof TerminalView)) { |
| // How did that happen? |
| continue; |
| } |
| |
| final TerminalView tv = (TerminalView) view; |
| |
| if (tv.bridge == bridge) { |
| return i; |
| } |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Displays the child in the ViewFlipper at the requestedIndex and updates the prompts. |
| * |
| * @param requestedIndex |
| * the index of the terminal view to display |
| */ |
| private void setDisplayedTerminal(int requestedIndex) { |
| synchronized (flip) { |
| try { |
| // show the requested bridge if found, also fade out overlay |
| flip.setDisplayedChild(requestedIndex); |
| flip.getCurrentView().findViewById(R.id.terminal_overlay).startAnimation(fade_out_delayed); |
| } catch (NullPointerException npe) { |
| Log.d("View went away when we were about to display it", npe); |
| } |
| updatePromptVisible(); |
| } |
| } |
| } |