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