/* * 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(); } } }