+ * EmulatorView supports changing its default foreground,
+ * background, and cursor colors. Passing a ColorScheme to
+ * {@link EmulatorView#setColorScheme setColorScheme} will cause the
+ * EmulatorView to use the specified colors as its defaults.
+ *
+ * Cursor colors can be omitted when specifying a color scheme; if no cursor
+ * colors are specified, ColorScheme will automatically select
+ * suitable cursor colors for you.
+ *
+ * @see EmulatorView#setColorScheme
+ */
+
+public class ColorScheme {
+ private int foreColor;
+ private int backColor;
+ private int cursorForeColor;
+ private int cursorBackColor;
+ final private static int sDefaultCursorBackColor = 0xff808080;
+
+ private void setDefaultCursorColors() {
+ cursorBackColor = sDefaultCursorBackColor;
+ // Use the foreColor unless the foreColor is too similar to the cursorBackColor
+ int foreDistance = distance(foreColor, cursorBackColor);
+ int backDistance = distance(backColor, cursorBackColor);
+ if (foreDistance * 2 >= backDistance) {
+ cursorForeColor = foreColor;
+ } else {
+ cursorForeColor = backColor;
+ }
+ }
+
+ private static int distance(int a, int b) {
+ return channelDistance(a, b, 0) * 3 + channelDistance(a, b, 1) * 5
+ + channelDistance(a, b, 2);
+ }
+
+ private static int channelDistance(int a, int b, int channel) {
+ return Math.abs(getChannel(a, channel) - getChannel(b, channel));
+ }
+
+ private static int getChannel(int color, int channel) {
+ return 0xff & (color >> ((2 - channel) * 8));
+ }
+
+ /**
+ * Creates a ColorScheme object.
+ *
+ * @param foreColor The foreground color as an ARGB hex value.
+ * @param backColor The background color as an ARGB hex value.
+ */
+ public ColorScheme(int foreColor, int backColor) {
+ this.foreColor = foreColor;
+ this.backColor = backColor;
+ setDefaultCursorColors();
+ }
+
+ /**
+ * Creates a ColorScheme object.
+ *
+ * @param foreColor The foreground color as an ARGB hex value.
+ * @param backColor The background color as an ARGB hex value.
+ * @param cursorForeColor The cursor foreground color as an ARGB hex value.
+ * @param cursorBackColor The cursor foreground color as an ARGB hex value.
+ */
+ public ColorScheme(int foreColor, int backColor, int cursorForeColor, int cursorBackColor) {
+ this.foreColor = foreColor;
+ this.backColor = backColor;
+ this.cursorForeColor = cursorForeColor;
+ this.cursorBackColor = cursorBackColor;
+ }
+
+ /**
+ * Creates a ColorScheme object from an array.
+ *
+ * @param scheme An integer array { foreColor, backColor,
+ * optionalCursorForeColor, optionalCursorBackColor }.
+ */
+ public ColorScheme(int[] scheme) {
+ int schemeLength = scheme.length;
+ if (schemeLength != 2 && schemeLength != 4) {
+ throw new IllegalArgumentException();
+ }
+ this.foreColor = scheme[0];
+ this.backColor = scheme[1];
+ if (schemeLength == 2) {
+ setDefaultCursorColors();
+ } else {
+ this.cursorForeColor = scheme[2];
+ this.cursorBackColor = scheme[3];
+ }
+ }
+
+ /**
+ * @return This ColorScheme's foreground color as an ARGB
+ * hex value.
+ */
+ public int getForeColor() {
+ return foreColor;
+ }
+
+ /**
+ * @return This ColorScheme's background color as an ARGB
+ * hex value.
+ */
+ public int getBackColor() {
+ return backColor;
+ }
+
+ /**
+ * @return This ColorScheme's cursor foreground color as an ARGB
+ * hex value.
+ */
+ public int getCursorForeColor() {
+ return cursorForeColor;
+ }
+
+ /**
+ * @return This ColorScheme's cursor background color as an ARGB
+ * hex value.
+ */
+ public int getCursorBackColor() {
+ return cursorBackColor;
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorDebug.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorDebug.java
new file mode 100644
index 000000000..ff84c0c2a
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorDebug.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.emulatorview;
+
+/**
+ * Debug settings.
+ */
+
+class EmulatorDebug {
+ /**
+ * Set to true to add debugging code and logging.
+ */
+ public static final boolean DEBUG = false;
+
+ /**
+ * Set to true to log IME calls.
+ */
+ public static final boolean LOG_IME = DEBUG & false;
+
+ /**
+ * Set to true to log each character received from the remote process to the
+ * android log, which makes it easier to debug some kinds of problems with
+ * emulating escape sequences and control codes.
+ */
+ public static final boolean LOG_CHARACTERS_FLAG = DEBUG & false;
+
+ /**
+ * Set to true to log unknown escape sequences.
+ */
+ public static final boolean LOG_UNKNOWN_ESCAPE_SEQUENCES = DEBUG & false;
+
+ /**
+ * The tag we use when logging, so that our messages can be distinguished
+ * from other messages in the log. Public because it's used by several
+ * classes.
+ */
+ public static final String LOG_TAG = "EmulatorView";
+
+ public static String bytesToString(byte[] data, int base, int length) {
+ StringBuilder buf = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ byte b = data[base + i];
+ if (b < 32 || b > 126) {
+ buf.append(String.format("\\x%02x", b));
+ } else {
+ buf.append((char)b);
+ }
+ }
+ return buf.toString();
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorView.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorView.java
new file mode 100644
index 000000000..ba192f90e
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorView.java
@@ -0,0 +1,1714 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.emulatorview;
+
+import jackpal.androidterm.emulatorview.compat.ClipboardManagerCompat;
+import jackpal.androidterm.emulatorview.compat.ClipboardManagerCompatFactory;
+import jackpal.androidterm.emulatorview.compat.KeycodeConstants;
+import jackpal.androidterm.emulatorview.compat.Patterns;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Hashtable;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.text.util.Linkify.MatchFilter;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.widget.Scroller;
+
+/**
+ * A view on a {@link TermSession}. Displays the terminal emulator's screen,
+ * provides access to its scrollback buffer, and passes input through to the
+ * terminal emulator.
+ *
+ * If this view is inflated from an XML layout, you need to call {@link
+ * #attachSession attachSession} and {@link #setDensity setDensity} before using
+ * the view. If creating this view from code, use the {@link
+ * #EmulatorView(Context, TermSession, DisplayMetrics)} constructor, which will
+ * take care of this for you.
+ */
+public class EmulatorView extends View implements GestureDetector.OnGestureListener {
+ private final static String TAG = "EmulatorView";
+ private final static boolean LOG_KEY_EVENTS = false;
+ private final static boolean LOG_IME = false;
+
+ /**
+ * We defer some initialization until we have been layed out in the view
+ * hierarchy. The boolean tracks when we know what our size is.
+ */
+ private boolean mKnownSize;
+
+ // Set if initialization was deferred because a TermSession wasn't attached
+ private boolean mDeferInit = false;
+
+ private int mVisibleWidth;
+ private int mVisibleHeight;
+
+ private TermSession mTermSession;
+
+ /**
+ * Total width of each character, in pixels
+ */
+ private float mCharacterWidth;
+
+ /**
+ * Total height of each character, in pixels
+ */
+ private int mCharacterHeight;
+
+ /**
+ * Top-of-screen margin
+ */
+ private int mTopOfScreenMargin;
+
+ /**
+ * Used to render text
+ */
+ private TextRenderer mTextRenderer;
+
+ /**
+ * Text size. Zero means 4 x 8 font.
+ */
+ private int mTextSize = 10;
+
+ private int mCursorBlink;
+
+ /**
+ * Color scheme (default foreground/background colors).
+ */
+ private ColorScheme mColorScheme = BaseTextRenderer.defaultColorScheme;
+
+ private Paint mForegroundPaint;
+
+ private Paint mBackgroundPaint;
+
+ private boolean mUseCookedIme;
+
+ /**
+ * Our terminal emulator.
+ */
+ private TerminalEmulator mEmulator;
+
+ /**
+ * The number of rows of text to display.
+ */
+ private int mRows;
+
+ /**
+ * The number of columns of text to display.
+ */
+ private int mColumns;
+
+ /**
+ * The number of columns that are visible on the display.
+ */
+
+ private int mVisibleColumns;
+
+ /*
+ * The number of rows that are visible on the view
+ */
+ private int mVisibleRows;
+
+ /**
+ * The top row of text to display. Ranges from -activeTranscriptRows to 0
+ */
+ private int mTopRow;
+
+ private int mLeftColumn;
+
+ private static final int CURSOR_BLINK_PERIOD = 1000;
+
+ private boolean mCursorVisible = true;
+
+ private boolean mIsSelectingText = false;
+
+ private boolean mBackKeySendsCharacter = false;
+ private int mControlKeyCode;
+ private int mFnKeyCode;
+ private boolean mIsControlKeySent = false;
+ private boolean mIsFnKeySent = false;
+
+ private boolean mMouseTracking;
+
+ private float mDensity;
+
+ private float mScaledDensity;
+ private static final int SELECT_TEXT_OFFSET_Y = -40;
+ private int mSelXAnchor = -1;
+ private int mSelYAnchor = -1;
+ private int mSelX1 = -1;
+ private int mSelY1 = -1;
+ private int mSelX2 = -1;
+ private int mSelY2 = -1;
+
+ /**
+ * Routing alt and meta keyCodes away from the IME allows Alt key processing to work on
+ * the Asus Transformer TF101.
+ * It doesn't seem to harm anything else, but it also doesn't seem to be
+ * required on other platforms.
+ *
+ * This test should be refined as we learn more.
+ */
+ private final static boolean sTrapAltAndMeta = Build.MODEL.contains("Transformer TF101");
+
+ private Runnable mBlinkCursor = new Runnable() {
+ public void run() {
+ if (mCursorBlink != 0) {
+ mCursorVisible = ! mCursorVisible;
+ mHandler.postDelayed(this, CURSOR_BLINK_PERIOD);
+ } else {
+ mCursorVisible = true;
+ }
+ // Perhaps just invalidate the character with the cursor.
+ invalidate();
+ }
+ };
+
+ private GestureDetector mGestureDetector;
+ private GestureDetector.OnGestureListener mExtGestureListener;
+ private Scroller mScroller;
+ private Runnable mFlingRunner = new Runnable() {
+ public void run() {
+ if (mScroller.isFinished()) {
+ return;
+ }
+ // Check whether mouse tracking was turned on during fling.
+ if (isMouseTrackingActive()) {
+ return;
+ }
+
+ boolean more = mScroller.computeScrollOffset();
+ int newTopRow = mScroller.getCurrY();
+ if (newTopRow != mTopRow) {
+ mTopRow = newTopRow;
+ invalidate();
+ }
+
+ if (more) {
+ post(this);
+ }
+
+ }
+ };
+
+ /**
+ *
+ * A hash table of underlying URLs to implement clickable links.
+ */
+ private Hashtable mLinkLayer = new Hashtable();
+
+ /**
+ * Accept links that start with http[s]:
+ */
+ private static class HttpMatchFilter implements MatchFilter {
+ public boolean acceptMatch(CharSequence s, int start, int end) {
+ return startsWith(s, start, end, "http:") ||
+ startsWith(s, start, end, "https:");
+ }
+
+ private boolean startsWith(CharSequence s, int start, int end,
+ String prefix) {
+ int prefixLen = prefix.length();
+ int fragmentLen = end - start;
+ if (prefixLen > fragmentLen) {
+ return false;
+ }
+ for (int i = 0; i < prefixLen; i++) {
+ if (s.charAt(start + i) != prefix.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static MatchFilter sHttpMatchFilter = new HttpMatchFilter();
+
+ /**
+ * Convert any URLs in the current row into a URLSpan,
+ * and store that result in a hash table of URLSpan entries.
+ *
+ * @param row The number of the row to check for links
+ * @return The number of lines in a multi-line-wrap set of links
+ */
+ private int createLinks(int row)
+ {
+ TranscriptScreen transcriptScreen = mEmulator.getScreen();
+ char [] line = transcriptScreen.getScriptLine(row);
+ int lineCount = 1;
+
+ //Nothing to do if there's no text.
+ if(line == null)
+ return lineCount;
+
+ /* If this is not a basic line, the array returned from getScriptLine()
+ * could have arbitrary garbage at the end -- find the point at which
+ * the line ends and only include that in the text to linkify.
+ *
+ * XXX: The fact that the array returned from getScriptLine() on a
+ * basic line contains no garbage is an implementation detail -- the
+ * documented behavior explicitly allows garbage at the end! */
+ int lineLen;
+ boolean textIsBasic = transcriptScreen.isBasicLine(row);
+ if (textIsBasic) {
+ lineLen = line.length;
+ } else {
+ // The end of the valid data is marked by a NUL character
+ for (lineLen = 0; line[lineLen] != 0; ++lineLen);
+ }
+
+ SpannableStringBuilder textToLinkify = new SpannableStringBuilder(new String(line, 0, lineLen));
+
+ boolean lineWrap = transcriptScreen.getScriptLineWrap(row);
+
+ //While the current line has a wrap
+ while (lineWrap)
+ {
+ //Get next line
+ int nextRow = row + lineCount;
+ line = transcriptScreen.getScriptLine(nextRow);
+
+ //If next line is blank, don't try and append
+ if(line == null)
+ break;
+
+ boolean lineIsBasic = transcriptScreen.isBasicLine(nextRow);
+ if (textIsBasic && !lineIsBasic) {
+ textIsBasic = lineIsBasic;
+ }
+ if (lineIsBasic) {
+ lineLen = line.length;
+ } else {
+ // The end of the valid data is marked by a NUL character
+ for (lineLen = 0; line[lineLen] != 0; ++lineLen);
+ }
+
+ textToLinkify.append(new String(line, 0, lineLen));
+
+ //Check if line after next is wrapped
+ lineWrap = transcriptScreen.getScriptLineWrap(nextRow);
+ ++lineCount;
+ }
+
+ Linkify.addLinks(textToLinkify, Patterns.WEB_URL,
+ null, sHttpMatchFilter, null);
+ URLSpan [] urls = textToLinkify.getSpans(0, textToLinkify.length(), URLSpan.class);
+ if(urls.length > 0)
+ {
+ int columns = mColumns;
+
+ //re-index row to 0 if it is negative
+ int screenRow = row - mTopRow;
+
+ //Create and initialize set of links
+ URLSpan [][] linkRows = new URLSpan[lineCount][];
+ for(int i=0; i= columns) {
+ ++startRow;
+ startCol %= columns;
+ }
+ }
+
+ endRow = startRow;
+ endCol = startCol;
+ for (int i = spanStart; i < spanEnd; ++i) {
+ char c = textToLinkify.charAt(i);
+ if (Character.isHighSurrogate(c)) {
+ ++i;
+ endCol += UnicodeTranscript.charWidth(c, textToLinkify.charAt(i));
+ } else {
+ endCol += UnicodeTranscript.charWidth(c);
+ }
+ if (endCol >= columns) {
+ ++endRow;
+ endCol %= columns;
+ }
+ }
+ }
+
+ //Fill linkRows with the URL where appropriate
+ for(int i=startRow; i <= endRow; ++i)
+ {
+ int runStart = (i == startRow) ? startCol: 0;
+ int runEnd = (i == endRow) ? endCol : mColumns - 1;
+
+ Arrays.fill(linkRows[i], runStart, runEnd + 1, url);
+ }
+ }
+
+ //Add links into the link layer for later retrieval
+ for(int i=0; i newY; mLastY--) {
+ sendMouseEventCode(mMotionEvent, 64);
+ }
+
+ if (more) {
+ post(this);
+ }
+ }
+ };
+ private MouseTrackingFlingRunner mMouseTrackingFlingRunner = new MouseTrackingFlingRunner();
+
+ private float mScrollRemainder;
+ private TermKeyListener mKeyListener;
+
+ private String mImeBuffer = "";
+
+ /**
+ * Our message handler class. Implements a periodic callback.
+ */
+ private final Handler mHandler = new Handler();
+
+ /**
+ * Called by the TermSession when the contents of the view need updating
+ */
+ private UpdateCallback mUpdateNotify = new UpdateCallback() {
+ public void onUpdate() {
+ if ( mIsSelectingText ) {
+ int rowShift = mEmulator.getScrollCounter();
+ mSelY1 -= rowShift;
+ mSelY2 -= rowShift;
+ mSelYAnchor -= rowShift;
+ }
+ mEmulator.clearScrollCounter();
+ ensureCursorVisible();
+ invalidate();
+ }
+ };
+
+ /**
+ * Create an EmulatorView for a {@link TermSession}.
+ *
+ * @param context The {@link Context} for the view.
+ * @param session The {@link TermSession} this view will be displaying.
+ * @param metrics The {@link DisplayMetrics} of the screen on which the view
+ * will be displayed.
+ */
+ public EmulatorView(Context context, TermSession session, DisplayMetrics metrics) {
+ super(context);
+ attachSession(session);
+ setDensity(metrics);
+ commonConstructor(context);
+ }
+
+ /**
+ * Constructor called when inflating this view from XML.
+ *
+ * You should call {@link #attachSession attachSession} and {@link
+ * #setDensity setDensity} before using an EmulatorView created
+ * using this constructor.
+ */
+ public EmulatorView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ commonConstructor(context);
+ }
+
+ /**
+ * Constructor called when inflating this view from XML with a
+ * default style set.
+ *
+ * You should call {@link #attachSession attachSession} and {@link
+ * #setDensity setDensity} before using an EmulatorView created
+ * using this constructor.
+ */
+ public EmulatorView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ commonConstructor(context);
+ }
+
+ private void commonConstructor(Context context) {
+ // TODO: See if we want to use the API level 11 constructor to get new flywheel feature.
+ mScroller = new Scroller(context);
+ mMouseTrackingFlingRunner.mScroller = new Scroller(context);
+ }
+
+ /**
+ * Attach a {@link TermSession} to this view.
+ *
+ * @param session The {@link TermSession} this view will be displaying.
+ */
+ public void attachSession(TermSession session) {
+ mTextRenderer = null;
+ mForegroundPaint = new Paint();
+ mBackgroundPaint = new Paint();
+ mTopRow = 0;
+ mLeftColumn = 0;
+ mGestureDetector = new GestureDetector(this);
+ // mGestureDetector.setIsLongpressEnabled(false);
+ setVerticalScrollBarEnabled(true);
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+
+ mTermSession = session;
+
+ mKeyListener = new TermKeyListener(session);
+ session.setKeyListener(mKeyListener);
+
+ // Do init now if it was deferred until a TermSession was attached
+ if (mDeferInit) {
+ mDeferInit = false;
+ mKnownSize = true;
+ initialize();
+ }
+ }
+
+ /**
+ * Update the screen density for the screen on which the view is displayed.
+ *
+ * @param metrics The {@link DisplayMetrics} of the screen.
+ */
+ public void setDensity(DisplayMetrics metrics) {
+ if (mDensity == 0) {
+ // First time we've known the screen density, so update font size
+ mTextSize = (int) (mTextSize * metrics.density);
+ }
+ mDensity = metrics.density;
+ mScaledDensity = metrics.scaledDensity;
+ }
+
+ /**
+ * Inform the view that it is now visible on screen.
+ */
+ public void onResume() {
+ updateSize(false);
+ if (mCursorBlink != 0) {
+ mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
+ }
+ if (mKeyListener != null) {
+ mKeyListener.onResume();
+ }
+ }
+
+ /**
+ * Inform the view that it is no longer visible on the screen.
+ */
+ public void onPause() {
+ if (mCursorBlink != 0) {
+ mHandler.removeCallbacks(mBlinkCursor);
+ }
+ if (mKeyListener != null) {
+ mKeyListener.onPause();
+ }
+ }
+
+ /**
+ * Set this EmulatorView's color scheme.
+ *
+ * @param scheme The {@link ColorScheme} to use (use null for the default
+ * scheme).
+ * @see TermSession#setColorScheme
+ * @see ColorScheme
+ */
+ public void setColorScheme(ColorScheme scheme) {
+ if (scheme == null) {
+ mColorScheme = BaseTextRenderer.defaultColorScheme;
+ } else {
+ mColorScheme = scheme;
+ }
+ updateText();
+ }
+
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return true;
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ outAttrs.inputType = mUseCookedIme ?
+ EditorInfo.TYPE_CLASS_TEXT :
+ EditorInfo.TYPE_NULL;
+ return new BaseInputConnection(this, true) {
+ /**
+ * Used to handle composing text requests
+ */
+ private int mCursor;
+ private int mComposingTextStart;
+ private int mComposingTextEnd;
+ private int mSelectedTextStart;
+ private int mSelectedTextEnd;
+
+ private void sendText(CharSequence text) {
+ int n = text.length();
+ char c;
+ try {
+ for(int i = 0; i < n; i++) {
+ c = text.charAt(i);
+ if (Character.isHighSurrogate(c)) {
+ int codePoint;
+ if (++i < n) {
+ codePoint = Character.toCodePoint(c, text.charAt(i));
+ } else {
+ // Unicode Replacement Glyph, aka white question mark in black diamond.
+ codePoint = '\ufffd';
+ }
+ mapAndSend(codePoint);
+ } else {
+ mapAndSend(c);
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "error writing ", e);
+ }
+ }
+
+ private void mapAndSend(int c) throws IOException {
+ int result = mKeyListener.mapControlChar(c);
+ if (result < TermKeyListener.KEYCODE_OFFSET) {
+ mTermSession.write(result);
+ } else {
+ mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, null, getKeypadApplicationMode());
+ }
+ clearSpecialKeyStatus();
+ }
+
+ public boolean beginBatchEdit() {
+ if (LOG_IME) {
+ Log.w(TAG, "beginBatchEdit");
+ }
+ setImeBuffer("");
+ mCursor = 0;
+ mComposingTextStart = 0;
+ mComposingTextEnd = 0;
+ return true;
+ }
+
+ public boolean clearMetaKeyStates(int arg0) {
+ if (LOG_IME) {
+ Log.w(TAG, "clearMetaKeyStates " + arg0);
+ }
+ return false;
+ }
+
+ public boolean commitCompletion(CompletionInfo arg0) {
+ if (LOG_IME) {
+ Log.w(TAG, "commitCompletion " + arg0);
+ }
+ return false;
+ }
+
+ public boolean endBatchEdit() {
+ if (LOG_IME) {
+ Log.w(TAG, "endBatchEdit");
+ }
+ return true;
+ }
+
+ public boolean finishComposingText() {
+ if (LOG_IME) {
+ Log.w(TAG, "finishComposingText");
+ }
+ sendText(mImeBuffer);
+ setImeBuffer("");
+ mComposingTextStart = 0;
+ mComposingTextEnd = 0;
+ mCursor = 0;
+ return true;
+ }
+
+ public int getCursorCapsMode(int reqModes) {
+ if (LOG_IME) {
+ Log.w(TAG, "getCursorCapsMode(" + reqModes + ")");
+ }
+ int mode = 0;
+ if ((reqModes & TextUtils.CAP_MODE_CHARACTERS) != 0) {
+ mode |= TextUtils.CAP_MODE_CHARACTERS;
+ }
+ return mode;
+ }
+
+ public ExtractedText getExtractedText(ExtractedTextRequest arg0,
+ int arg1) {
+ if (LOG_IME) {
+ Log.w(TAG, "getExtractedText" + arg0 + "," + arg1);
+ }
+ return null;
+ }
+
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ if (LOG_IME) {
+ Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")");
+ }
+ int len = Math.min(n, mImeBuffer.length() - mCursor);
+ if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
+ return "";
+ }
+ return mImeBuffer.substring(mCursor, mCursor + len);
+ }
+
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ if (LOG_IME) {
+ Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")");
+ }
+ int len = Math.min(n, mCursor);
+ if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
+ return "";
+ }
+ return mImeBuffer.substring(mCursor-len, mCursor);
+ }
+
+ public boolean performContextMenuAction(int arg0) {
+ if (LOG_IME) {
+ Log.w(TAG, "performContextMenuAction" + arg0);
+ }
+ return true;
+ }
+
+ public boolean performPrivateCommand(String arg0, Bundle arg1) {
+ if (LOG_IME) {
+ Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1);
+ }
+ return true;
+ }
+
+ public boolean reportFullscreenMode(boolean arg0) {
+ if (LOG_IME) {
+ Log.w(TAG, "reportFullscreenMode" + arg0);
+ }
+ return true;
+ }
+
+ public boolean commitCorrection (CorrectionInfo correctionInfo) {
+ if (LOG_IME) {
+ Log.w(TAG, "commitCorrection");
+ }
+ return true;
+ }
+
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ if (LOG_IME) {
+ Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")");
+ }
+ clearComposingText();
+ sendText(text);
+ setImeBuffer("");
+ mCursor = 0;
+ return true;
+ }
+
+ private void clearComposingText() {
+ int len = mImeBuffer.length();
+ if (mComposingTextStart > len || mComposingTextEnd > len) {
+ mComposingTextEnd = mComposingTextStart = 0;
+ return;
+ }
+ setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
+ mImeBuffer.substring(mComposingTextEnd));
+ if (mCursor < mComposingTextStart) {
+ // do nothing
+ } else if (mCursor < mComposingTextEnd) {
+ mCursor = mComposingTextStart;
+ } else {
+ mCursor -= mComposingTextEnd - mComposingTextStart;
+ }
+ mComposingTextEnd = mComposingTextStart = 0;
+ }
+
+ public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ if (LOG_IME) {
+ Log.w(TAG, "deleteSurroundingText(" + leftLength +
+ "," + rightLength + ")");
+ }
+ if (leftLength > 0) {
+ for (int i = 0; i < leftLength; i++) {
+ sendKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ }
+ } else if ((leftLength == 0) && (rightLength == 0)) {
+ // Delete key held down / repeating
+ sendKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ }
+ // TODO: handle forward deletes.
+ return true;
+ }
+
+ public boolean performEditorAction(int actionCode) {
+ if (LOG_IME) {
+ Log.w(TAG, "performEditorAction(" + actionCode + ")");
+ }
+ if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
+ // The "return" key has been pressed on the IME.
+ sendText("\r");
+ }
+ return true;
+ }
+
+ public boolean sendKeyEvent(KeyEvent event) {
+ if (LOG_IME) {
+ Log.w(TAG, "sendKeyEvent(" + event + ")");
+ }
+ // Some keys are sent here rather than to commitText.
+ // In particular, del and the digit keys are sent here.
+ // (And I have reports that the HTC Magic also sends Return here.)
+ // As a bit of defensive programming, handle every key.
+ dispatchKeyEvent(event);
+ return true;
+ }
+
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ if (LOG_IME) {
+ Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")");
+ }
+ int len = mImeBuffer.length();
+ if (mComposingTextStart > len || mComposingTextEnd > len) {
+ return false;
+ }
+ setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
+ text + mImeBuffer.substring(mComposingTextEnd));
+ mComposingTextEnd = mComposingTextStart + text.length();
+ mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1
+ : mComposingTextStart - newCursorPosition;
+ return true;
+ }
+
+ public boolean setSelection(int start, int end) {
+ if (LOG_IME) {
+ Log.w(TAG, "setSelection" + start + "," + end);
+ }
+ int length = mImeBuffer.length();
+ if (start == end && start > 0 && start < length) {
+ mSelectedTextStart = mSelectedTextEnd = 0;
+ mCursor = start;
+ } else if (start < end && start > 0 && end < length) {
+ mSelectedTextStart = start;
+ mSelectedTextEnd = end;
+ mCursor = start;
+ }
+ return true;
+ }
+
+ public boolean setComposingRegion(int start, int end) {
+ if (LOG_IME) {
+ Log.w(TAG, "setComposingRegion " + start + "," + end);
+ }
+ if (start < end && start > 0 && end < mImeBuffer.length()) {
+ clearComposingText();
+ mComposingTextStart = start;
+ mComposingTextEnd = end;
+ }
+ return true;
+ }
+
+ public CharSequence getSelectedText(int flags) {
+ if (LOG_IME) {
+ Log.w(TAG, "getSelectedText " + flags);
+ }
+ int len = mImeBuffer.length();
+ if (mSelectedTextEnd >= len || mSelectedTextStart > mSelectedTextEnd) {
+ return "";
+ }
+ return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1);
+ }
+
+ };
+ }
+
+ private void setImeBuffer(String buffer) {
+ if (!buffer.equals(mImeBuffer)) {
+ invalidate();
+ }
+ mImeBuffer = buffer;
+ }
+
+ /**
+ * Get the terminal emulator's keypad application mode.
+ */
+ public boolean getKeypadApplicationMode() {
+ return mEmulator.getKeypadApplicationMode();
+ }
+
+ /**
+ * Set a {@link android.view.GestureDetector.OnGestureListener
+ * GestureDetector.OnGestureListener} to receive gestures performed on this
+ * view. Can be used to implement additional
+ * functionality via touch gestures or override built-in gestures.
+ *
+ * @param listener The {@link
+ * android.view.GestureDetector.OnGestureListener
+ * GestureDetector.OnGestureListener} which will receive
+ * gestures.
+ */
+ public void setExtGestureListener(GestureDetector.OnGestureListener listener) {
+ mExtGestureListener = listener;
+ }
+
+ /**
+ * Compute the vertical range that the vertical scrollbar represents.
+ */
+ @Override
+ protected int computeVerticalScrollRange() {
+ return mEmulator.getScreen().getActiveRows();
+ }
+
+ /**
+ * Compute the vertical extent of the horizontal scrollbar's thumb within
+ * the vertical range. This value is used to compute the length of the thumb
+ * within the scrollbar's track.
+ */
+ @Override
+ protected int computeVerticalScrollExtent() {
+ return mRows;
+ }
+
+ /**
+ * Compute the vertical offset of the vertical scrollbar's thumb within the
+ * horizontal range. This value is used to compute the position of the thumb
+ * within the scrollbar's track.
+ */
+ @Override
+ protected int computeVerticalScrollOffset() {
+ return mEmulator.getScreen().getActiveRows() + mTopRow - mRows;
+ }
+
+ /**
+ * Call this to initialize the view.
+ */
+ private void initialize() {
+ TermSession session = mTermSession;
+
+ updateText();
+
+ mEmulator = session.getEmulator();
+ session.setUpdateCallback(mUpdateNotify);
+
+ requestFocus();
+ }
+
+ /**
+ * Get the {@link TermSession} corresponding to this view.
+ *
+ * @return The {@link TermSession} object for this view.
+ */
+ public TermSession getTermSession() {
+ return mTermSession;
+ }
+
+ /**
+ * Get the width of the visible portion of this view.
+ *
+ * @return The width of the visible portion of this view, in pixels.
+ */
+ public int getVisibleWidth() {
+ return mVisibleWidth;
+ }
+
+ /**
+ * Get the height of the visible portion of this view.
+ *
+ * @return The height of the visible portion of this view, in pixels.
+ */
+ public int getVisibleHeight() {
+ return mVisibleHeight;
+ }
+
+ /**
+ * Gets the visible number of rows for the view, useful when updating Ptysize with the correct number of rows/columns
+ * @return The rows for the visible number of rows, this is calculate in updateSize(int w, int h), please call
+ * updateSize(true) if the view changed, to get the correct calculation before calling this.
+ */
+ public int getVisibleRows()
+ {
+ return mVisibleRows;
+ }
+
+ /**
+ * Gets the visible number of columns for the view, again useful to get when updating PTYsize
+ * @return the columns for the visisble view, please call updateSize(true) to re-calculate this if the view has changed
+ */
+ public int getVisibleColumns()
+ {
+ return mVisibleColumns;
+ }
+
+
+ /**
+ * Page the terminal view (scroll it up or down by delta
+ * screenfuls).
+ *
+ * @param delta The number of screens to scroll. Positive means scroll down,
+ * negative means scroll up.
+ */
+ public void page(int delta) {
+ mTopRow =
+ Math.min(0, Math.max(-(mEmulator.getScreen()
+ .getActiveTranscriptRows()), mTopRow + mRows * delta));
+ invalidate();
+ }
+
+ /**
+ * Page the terminal view horizontally.
+ *
+ * @param deltaColumns the number of columns to scroll. Positive scrolls to
+ * the right.
+ */
+ public void pageHorizontal(int deltaColumns) {
+ mLeftColumn =
+ Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns
+ - mVisibleColumns));
+ invalidate();
+ }
+
+ /**
+ * Sets the text size, which in turn sets the number of rows and columns.
+ *
+ * @param fontSize the new font size, in density-independent pixels.
+ */
+ public void setTextSize(int fontSize) {
+ mTextSize = (int) (fontSize * mDensity);
+ updateText();
+ }
+
+ /**
+ * Sets the IME mode ("cooked" or "raw").
+ *
+ * @param useCookedIME Whether the IME should be used in cooked mode.
+ */
+ public void setUseCookedIME(boolean useCookedIME) {
+ mUseCookedIme = useCookedIME;
+ }
+
+ /**
+ * Returns true if mouse events are being sent as escape sequences to the terminal.
+ */
+ public boolean isMouseTrackingActive() {
+ return mEmulator.getMouseTrackingMode() != 0 && mMouseTracking;
+ }
+
+ /**
+ * Send a single mouse event code to the terminal.
+ */
+ private void sendMouseEventCode(MotionEvent e, int button_code) {
+ int x = (int)(e.getX() / mCharacterWidth) + 1;
+ int y = (int)((e.getY()-mTopOfScreenMargin) / mCharacterHeight) + 1;
+ // Clip to screen, and clip to the limits of 8-bit data.
+ boolean out_of_bounds =
+ x < 1 || y < 1 ||
+ x > mColumns || y > mRows ||
+ x > 255-32 || y > 255-32;
+ //Log.d(TAG, "mouse button "+x+","+y+","+button_code+",oob="+out_of_bounds);
+ if(button_code < 0 || button_code > 255-32) {
+ Log.e(TAG, "mouse button_code out of range: "+button_code);
+ return;
+ }
+ if(!out_of_bounds) {
+ byte[] data = {
+ '\033', '[', 'M',
+ (byte)(32 + button_code),
+ (byte)(32 + x),
+ (byte)(32 + y) };
+ mTermSession.write(data, 0, data.length);
+ }
+ }
+
+ // Begin GestureDetector.OnGestureListener methods
+
+ public boolean onSingleTapUp(MotionEvent e) {
+ if (mExtGestureListener != null && mExtGestureListener.onSingleTapUp(e)) {
+ return true;
+ }
+
+ if (isMouseTrackingActive()) {
+ sendMouseEventCode(e, 0); // BTN1 press
+ sendMouseEventCode(e, 3); // release
+ }
+
+ requestFocus();
+ return true;
+ }
+
+ public void onLongPress(MotionEvent e) {
+ // XXX hook into external gesture listener
+ showContextMenu();
+ }
+
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+ if (mExtGestureListener != null && mExtGestureListener.onScroll(e1, e2, distanceX, distanceY)) {
+ return true;
+ }
+
+ distanceY += mScrollRemainder;
+ int deltaRows = (int) (distanceY / mCharacterHeight);
+ mScrollRemainder = distanceY - deltaRows * mCharacterHeight;
+
+ if (isMouseTrackingActive()) {
+ // Send mouse wheel events to terminal.
+ for (; deltaRows>0; deltaRows--) {
+ sendMouseEventCode(e1, 65);
+ }
+ for (; deltaRows<0; deltaRows++) {
+ sendMouseEventCode(e1, 64);
+ }
+ return true;
+ }
+
+ mTopRow =
+ Math.min(0, Math.max(-(mEmulator.getScreen()
+ .getActiveTranscriptRows()), mTopRow + deltaRows));
+ invalidate();
+
+ return true;
+ }
+
+ public void onSingleTapConfirmed(MotionEvent e) {
+ }
+
+ public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) {
+ // Scroll to bottom
+ mTopRow = 0;
+ invalidate();
+ return true;
+ }
+
+ public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) {
+ // Scroll to top
+ mTopRow = -mEmulator.getScreen().getActiveTranscriptRows();
+ invalidate();
+ return true;
+ }
+
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ if (mExtGestureListener != null && mExtGestureListener.onFling(e1, e2, velocityX, velocityY)) {
+ return true;
+ }
+
+ mScrollRemainder = 0.0f;
+ if (isMouseTrackingActive()) {
+ mMouseTrackingFlingRunner.fling(e1, velocityX, velocityY);
+ } else {
+ float SCALE = 0.25f;
+ mScroller.fling(0, mTopRow,
+ -(int) (velocityX * SCALE), -(int) (velocityY * SCALE),
+ 0, 0,
+ -mEmulator.getScreen().getActiveTranscriptRows(), 0);
+ // onScroll(e1, e2, 0.1f * velocityX, -0.1f * velocityY);
+ post(mFlingRunner);
+ }
+ return true;
+ }
+
+ public void onShowPress(MotionEvent e) {
+ if (mExtGestureListener != null) {
+ mExtGestureListener.onShowPress(e);
+ }
+ }
+
+ public boolean onDown(MotionEvent e) {
+ if (mExtGestureListener != null && mExtGestureListener.onDown(e)) {
+ return true;
+ }
+ mScrollRemainder = 0.0f;
+ return true;
+ }
+
+ // End GestureDetector.OnGestureListener methods
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mIsSelectingText) {
+ return onTouchEventWhileSelectingText(ev);
+ } else {
+ return mGestureDetector.onTouchEvent(ev);
+ }
+ }
+
+ private boolean onTouchEventWhileSelectingText(MotionEvent ev) {
+ int action = ev.getAction();
+ int cx = (int)(ev.getX() / mCharacterWidth);
+ int cy = Math.max(0,
+ (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity)
+ / mCharacterHeight) + mTopRow);
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mSelXAnchor = cx;
+ mSelYAnchor = cy;
+ mSelX1 = cx;
+ mSelY1 = cy;
+ mSelX2 = mSelX1;
+ mSelY2 = mSelY1;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_UP:
+ int minx = Math.min(mSelXAnchor, cx);
+ int maxx = Math.max(mSelXAnchor, cx);
+ int miny = Math.min(mSelYAnchor, cy);
+ int maxy = Math.max(mSelYAnchor, cy);
+ mSelX1 = minx;
+ mSelY1 = miny;
+ mSelX2 = maxx;
+ mSelY2 = maxy;
+ if (action == MotionEvent.ACTION_UP) {
+ ClipboardManagerCompat clip = ClipboardManagerCompatFactory
+ .getManager(getContext().getApplicationContext());
+ clip.setText(getSelectedText().trim());
+ toggleSelectingText();
+ }
+ invalidate();
+ break;
+ default:
+ toggleSelectingText();
+ invalidate();
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Called when a key is pressed in the view.
+ *
+ * @param keyCode The keycode of the key which was pressed.
+ * @param event A {@link KeyEvent} describing the event.
+ * @return Whether the event was handled.
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (LOG_KEY_EVENTS) {
+ Log.w(TAG, "onKeyDown " + keyCode);
+ }
+ if (handleControlKey(keyCode, true)) {
+ return true;
+ } else if (handleFnKey(keyCode, true)) {
+ return true;
+ } else if (isSystemKey(keyCode, event)) {
+ if (! isInterceptedSystemKey(keyCode) ) {
+ // Don't intercept the system keys
+ return super.onKeyDown(keyCode, event);
+ }
+ }
+
+ // Translate the keyCode into an ASCII character.
+
+ try {
+ int oldCombiningAccent = mKeyListener.getCombiningAccent();
+ int oldCursorMode = mKeyListener.getCursorMode();
+ mKeyListener.keyDown(keyCode, event, getKeypadApplicationMode(),
+ TermKeyListener.isEventFromToggleDevice(event));
+ if (mKeyListener.getCombiningAccent() != oldCombiningAccent
+ || mKeyListener.getCursorMode() != oldCursorMode) {
+ invalidate();
+ }
+ } catch (IOException e) {
+ // Ignore I/O exceptions
+ }
+ return true;
+ }
+
+ /** Do we want to intercept this system key? */
+ private boolean isInterceptedSystemKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_BACK && mBackKeySendsCharacter;
+ }
+
+ /**
+ * Called when a key is released in the view.
+ *
+ * @param keyCode The keycode of the key which was released.
+ * @param event A {@link KeyEvent} describing the event.
+ * @return Whether the event was handled.
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (LOG_KEY_EVENTS) {
+ Log.w(TAG, "onKeyUp " + keyCode);
+ }
+ if (handleControlKey(keyCode, false)) {
+ return true;
+ } else if (handleFnKey(keyCode, false)) {
+ return true;
+ } else if (isSystemKey(keyCode, event)) {
+ // Don't intercept the system keys
+ if ( ! isInterceptedSystemKey(keyCode) ) {
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+
+ mKeyListener.keyUp(keyCode, event);
+ clearSpecialKeyStatus();
+ return true;
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (sTrapAltAndMeta) {
+ boolean altEsc = mKeyListener.getAltSendsEsc();
+ boolean altOn = (event.getMetaState() & KeyEvent.META_ALT_ON) != 0;
+ boolean metaOn = (event.getMetaState() & KeyEvent.META_META_ON) != 0;
+ boolean altPressed = (keyCode == KeyEvent.KEYCODE_ALT_LEFT)
+ || (keyCode == KeyEvent.KEYCODE_ALT_RIGHT);
+ boolean altActive = mKeyListener.isAltActive();
+ if (altEsc && (altOn || altPressed || altActive || metaOn)) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ return onKeyDown(keyCode, event);
+ } else {
+ return onKeyUp(keyCode, event);
+ }
+ }
+ }
+
+ if (handleHardwareControlKey(keyCode, event)) {
+ return true;
+ }
+
+ if (mKeyListener.isCtrlActive()) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ return onKeyDown(keyCode, event);
+ } else {
+ return onKeyUp(keyCode, event);
+ }
+ }
+
+ return super.onKeyPreIme(keyCode, event);
+ };
+
+ private boolean handleControlKey(int keyCode, boolean down) {
+ if (keyCode == mControlKeyCode) {
+ if (LOG_KEY_EVENTS) {
+ Log.w(TAG, "handleControlKey " + keyCode);
+ }
+ mKeyListener.handleControlKey(down);
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean handleHardwareControlKey(int keyCode, KeyEvent event) {
+ if (keyCode == KeycodeConstants.KEYCODE_CTRL_LEFT ||
+ keyCode == KeycodeConstants.KEYCODE_CTRL_RIGHT) {
+ if (LOG_KEY_EVENTS) {
+ Log.w(TAG, "handleHardwareControlKey " + keyCode);
+ }
+ boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ mKeyListener.handleHardwareControlKey(down);
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean handleFnKey(int keyCode, boolean down) {
+ if (keyCode == mFnKeyCode) {
+ if (LOG_KEY_EVENTS) {
+ Log.w(TAG, "handleFnKey " + keyCode);
+ }
+ mKeyListener.handleFnKey(down);
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSystemKey(int keyCode, KeyEvent event) {
+ return event.isSystem();
+ }
+
+ private void clearSpecialKeyStatus() {
+ if (mIsControlKeySent) {
+ mIsControlKeySent = false;
+ mKeyListener.handleControlKey(false);
+ invalidate();
+ }
+ if (mIsFnKeySent) {
+ mIsFnKeySent = false;
+ mKeyListener.handleFnKey(false);
+ invalidate();
+ }
+ }
+
+ private void updateText() {
+ ColorScheme scheme = mColorScheme;
+ if (mTextSize > 0) {
+ mTextRenderer = new PaintRenderer(mTextSize, scheme);
+ }
+ else {
+ mTextRenderer = new Bitmap4x8FontRenderer(getResources(), scheme);
+ }
+
+ mForegroundPaint.setColor(scheme.getForeColor());
+ mBackgroundPaint.setColor(scheme.getBackColor());
+ mCharacterWidth = mTextRenderer.getCharacterWidth();
+ mCharacterHeight = mTextRenderer.getCharacterHeight();
+
+ updateSize(true);
+ }
+
+ /**
+ * This is called during layout when the size of this view has changed. If
+ * you were just added to the view hierarchy, you're called with the old
+ * values of 0.
+ */
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (mTermSession == null) {
+ // Not ready, defer until TermSession is attached
+ mDeferInit = true;
+ return;
+ }
+
+ if (!mKnownSize) {
+ mKnownSize = true;
+ initialize();
+ } else {
+ updateSize(false);
+ }
+ }
+
+ private void updateSize(int w, int h) {
+ mColumns = Math.max(1, (int) (((float) w) / mCharacterWidth));
+ mVisibleColumns = Math.max(1, (int) (((float) mVisibleWidth) / mCharacterWidth));
+
+ mTopOfScreenMargin = mTextRenderer.getTopMargin();
+ mRows = Math.max(1, (h - mTopOfScreenMargin) / mCharacterHeight);
+ mVisibleRows = Math.max(1, (mVisibleHeight - mTopOfScreenMargin) / mCharacterHeight);
+ mTermSession.updateSize(mColumns, mRows);
+
+ // Reset our paging:
+ mTopRow = 0;
+ mLeftColumn = 0;
+
+ invalidate();
+ }
+
+ /**
+ * Update the view's idea of its size.
+ *
+ * @param force Whether a size adjustment should be performed even if the
+ * view's size has not changed.
+ */
+ public void updateSize(boolean force) {
+ //Need to clear saved links on each display refresh
+ mLinkLayer.clear();
+ if (mKnownSize) {
+ int w = getWidth();
+ int h = getHeight();
+ // Log.w("Term", "(" + w + ", " + h + ")");
+ if (force || w != mVisibleWidth || h != mVisibleHeight) {
+ mVisibleWidth = w;
+ mVisibleHeight = h;
+ updateSize(mVisibleWidth, mVisibleHeight);
+ }
+ }
+ }
+
+ /**
+ * Draw the view to the provided {@link Canvas}.
+ *
+ * @param canvas The {@link Canvas} to draw the view to.
+ */
+ @Override
+ protected void onDraw(Canvas canvas) {
+ updateSize(false);
+
+ if (mEmulator == null) {
+ // Not ready yet
+ return;
+ }
+
+ int w = getWidth();
+ int h = getHeight();
+
+ boolean reverseVideo = mEmulator.getReverseVideo();
+ mTextRenderer.setReverseVideo(reverseVideo);
+
+ Paint backgroundPaint =
+ reverseVideo ? mForegroundPaint : mBackgroundPaint;
+ canvas.drawRect(0, 0, w, h, backgroundPaint);
+ float x = -mLeftColumn * mCharacterWidth;
+ float y = mCharacterHeight + mTopOfScreenMargin;
+ int endLine = mTopRow + mRows;
+ int cx = mEmulator.getCursorCol();
+ int cy = mEmulator.getCursorRow();
+ boolean cursorVisible = mCursorVisible && mEmulator.getShowCursor();
+ String effectiveImeBuffer = mImeBuffer;
+ int combiningAccent = mKeyListener.getCombiningAccent();
+ if (combiningAccent != 0) {
+ effectiveImeBuffer += String.valueOf((char) combiningAccent);
+ }
+ int cursorStyle = mKeyListener.getCursorMode();
+
+ int linkLinesToSkip = 0; //for multi-line links
+
+ for (int i = mTopRow; i < endLine; i++) {
+ int cursorX = -1;
+ if (i == cy && cursorVisible) {
+ cursorX = cx;
+ }
+ int selx1 = -1;
+ int selx2 = -1;
+ if ( i >= mSelY1 && i <= mSelY2 ) {
+ if ( i == mSelY1 ) {
+ selx1 = mSelX1;
+ }
+ if ( i == mSelY2 ) {
+ selx2 = mSelX2;
+ } else {
+ selx2 = mColumns;
+ }
+ }
+ mEmulator.getScreen().drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, effectiveImeBuffer, cursorStyle);
+ y += mCharacterHeight;
+ //if no lines to skip, create links for the line being drawn
+ if(linkLinesToSkip == 0)
+ linkLinesToSkip = createLinks(i);
+
+ //createLinks always returns at least 1
+ --linkLinesToSkip;
+ }
+ }
+
+ private void ensureCursorVisible() {
+ mTopRow = 0;
+ if (mVisibleColumns > 0) {
+ int cx = mEmulator.getCursorCol();
+ int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn;
+ if (visibleCursorX < 0) {
+ mLeftColumn = cx;
+ } else if (visibleCursorX >= mVisibleColumns) {
+ mLeftColumn = (cx - mVisibleColumns) + 1;
+ }
+ }
+ }
+
+ /**
+ * Toggle text selection mode in the view.
+ */
+ public void toggleSelectingText() {
+ mIsSelectingText = ! mIsSelectingText;
+ setVerticalScrollBarEnabled( ! mIsSelectingText );
+ if ( ! mIsSelectingText ) {
+ mSelX1 = -1;
+ mSelY1 = -1;
+ mSelX2 = -1;
+ mSelY2 = -1;
+ }
+ }
+
+ /**
+ * Whether the view is currently in text selection mode.
+ */
+ public boolean getSelectingText() {
+ return mIsSelectingText;
+ }
+
+ /**
+ * Get selected text.
+ *
+ * @return A {@link String} with the selected text.
+ */
+ public String getSelectedText() {
+ return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2);
+ }
+
+ /**
+ * Send a Ctrl key event to the terminal.
+ */
+ public void sendControlKey() {
+ mIsControlKeySent = true;
+ mKeyListener.handleControlKey(true);
+ invalidate();
+ }
+
+ /**
+ * Send an Fn key event to the terminal. The Fn modifier key can be used to
+ * generate various special characters and escape codes.
+ */
+ public void sendFnKey() {
+ mIsFnKeySent = true;
+ mKeyListener.handleFnKey(true);
+ invalidate();
+ }
+
+ /**
+ * Set the key code to be sent when the Back key is pressed.
+ */
+ public void setBackKeyCharacter(int keyCode) {
+ mKeyListener.setBackKeyCharacter(keyCode);
+ mBackKeySendsCharacter = (keyCode != 0);
+ }
+
+ /**
+ * Set whether to prepend the ESC keycode to the character when when pressing
+ * the ALT Key.
+ * @param flag
+ */
+ public void setAltSendsEsc(boolean flag) {
+ mKeyListener.setAltSendsEsc(flag);
+ }
+
+ /**
+ * Set the keycode corresponding to the Ctrl key.
+ */
+ public void setControlKeyCode(int keyCode) {
+ mControlKeyCode = keyCode;
+ }
+
+ /**
+ * Set the keycode corresponding to the Fn key.
+ */
+ public void setFnKeyCode(int keyCode) {
+ mFnKeyCode = keyCode;
+ }
+
+ public void setTermType(String termType) {
+ mKeyListener.setTermType(termType);
+ }
+
+ /**
+ * Set whether mouse events should be sent to the terminal as escape codes.
+ */
+ public void setMouseTracking(boolean flag) {
+ mMouseTracking = flag;
+ }
+
+
+ /**
+ * Get the URL for the link displayed at the specified screen coordinates.
+ *
+ * @param x The x coordinate being queried (from 0 to screen width)
+ * @param y The y coordinate being queried (from 0 to screen height)
+ * @return The URL for the link at the specified screen coordinates, or
+ * null if no link exists there.
+ */
+ public String getURLat(float x, float y)
+ {
+ float w = getWidth();
+ float h = getHeight();
+
+ //Check for division by zero
+ //If width or height is zero, there are probably no links around, so return null.
+ if(w == 0 || h == 0)
+ return null;
+
+ //Get fraction of total screen
+ float x_pos = x / w;
+ float y_pos = y / h;
+
+ //Convert to integer row/column index
+ int row = (int)Math.floor(y_pos * mRows);
+ int col = (int)Math.floor(x_pos * mColumns);
+
+ //Grab row from link layer
+ URLSpan [] linkRow = mLinkLayer.get(row);
+ URLSpan link;
+
+ //If row exists, and link exists at column, return it
+ if(linkRow != null && (link = linkRow[col]) != null)
+ return link.getURL();
+ else
+ return null;
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/GrowableIntArray.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/GrowableIntArray.java
new file mode 100644
index 000000000..9576ae713
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/GrowableIntArray.java
@@ -0,0 +1,29 @@
+package jackpal.androidterm.emulatorview;
+
+class GrowableIntArray {
+ GrowableIntArray(int initalCapacity) {
+ mData = new int[initalCapacity];
+ mLength = 0;
+ }
+
+ void append(int i) {
+ if (mLength + 1 > mData.length) {
+ int newLength = Math.max((mData.length * 3) >> 1, 16);
+ int[] temp = new int[newLength];
+ System.arraycopy(mData, 0, temp, 0, mLength);
+ mData = temp;
+ }
+ mData[mLength++] = i;
+ }
+
+ int length() {
+ return mLength;
+ }
+
+ int at(int index) {
+ return mData[index];
+ }
+
+ int[] mData;
+ int mLength;
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/PaintRenderer.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/PaintRenderer.java
new file mode 100644
index 000000000..4b2137a34
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/PaintRenderer.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.emulatorview;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.util.FloatMath;
+
+
+class PaintRenderer extends BaseTextRenderer {
+ public PaintRenderer(int fontSize, ColorScheme scheme) {
+ super(scheme);
+ mTextPaint = new Paint();
+ mTextPaint.setTypeface(Typeface.MONOSPACE);
+ mTextPaint.setAntiAlias(true);
+ mTextPaint.setTextSize(fontSize);
+
+ mCharHeight = (int) FloatMath.ceil(mTextPaint.getFontSpacing());
+ mCharAscent = (int) FloatMath.ceil(mTextPaint.ascent());
+ mCharDescent = mCharHeight + mCharAscent;
+ mCharWidth = mTextPaint.measureText(EXAMPLE_CHAR, 0, 1);
+ }
+
+ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset,
+ int runWidth, char[] text, int index, int count,
+ boolean selectionStyle, int textStyle,
+ int cursorOffset, int cursorIndex, int cursorIncr, int cursorWidth, int cursorMode) {
+ int foreColor = TextStyle.decodeForeColor(textStyle);
+ int backColor = TextStyle.decodeBackColor(textStyle);
+ int effect = TextStyle.decodeEffect(textStyle);
+
+ boolean inverse = mReverseVideo ^
+ (effect & (TextStyle.fxInverse | TextStyle.fxItalic)) != 0;
+ if (inverse) {
+ int temp = foreColor;
+ foreColor = backColor;
+ backColor = temp;
+ }
+
+ if (selectionStyle) {
+ backColor = TextStyle.ciCursorBackground;
+ }
+
+ boolean blink = (effect & TextStyle.fxBlink) != 0;
+ if (blink && backColor < 8) {
+ backColor += 8;
+ }
+ mTextPaint.setColor(mPalette[backColor]);
+
+ float left = x + lineOffset * mCharWidth;
+ canvas.drawRect(left, y + mCharAscent - mCharDescent,
+ left + runWidth * mCharWidth, y,
+ mTextPaint);
+
+ boolean cursorVisible = lineOffset <= cursorOffset && cursorOffset < (lineOffset + runWidth);
+ float cursorX = 0;
+ if (cursorVisible) {
+ cursorX = x + cursorOffset * mCharWidth;
+ drawCursorImp(canvas, (int) cursorX, y, cursorWidth * mCharWidth, mCharHeight, cursorMode);
+ }
+
+ boolean invisible = (effect & TextStyle.fxInvisible) != 0;
+ if (!invisible) {
+ boolean bold = (effect & TextStyle.fxBold) != 0;
+ boolean underline = (effect & TextStyle.fxUnderline) != 0;
+ if (bold) {
+ mTextPaint.setFakeBoldText(true);
+ }
+ if (underline) {
+ mTextPaint.setUnderlineText(true);
+ }
+ int textPaintColor;
+ if (foreColor < 8 && bold) {
+ // In 16-color mode, bold also implies bright foreground colors
+ textPaintColor = mPalette[foreColor+8];
+ } else {
+ textPaintColor = mPalette[foreColor];
+ }
+ mTextPaint.setColor(textPaintColor);
+
+ float textOriginY = y - mCharDescent;
+
+ if (cursorVisible) {
+ // Text before cursor
+ int countBeforeCursor = cursorIndex - index;
+ int countAfterCursor = count - (countBeforeCursor + cursorIncr);
+ if (countBeforeCursor > 0){
+ canvas.drawText(text, index, countBeforeCursor, left, textOriginY, mTextPaint);
+ }
+ // Text at cursor
+ mTextPaint.setColor(mPalette[TextStyle.ciCursorForeground]);
+ canvas.drawText(text, cursorIndex, cursorIncr, cursorX,
+ textOriginY, mTextPaint);
+ // Text after cursor
+ if (countAfterCursor > 0) {
+ mTextPaint.setColor(textPaintColor);
+ canvas.drawText(text, cursorIndex + cursorIncr, countAfterCursor,
+ cursorX + cursorWidth * mCharWidth,
+ textOriginY, mTextPaint);
+ }
+ } else {
+ canvas.drawText(text, index, count, left, textOriginY, mTextPaint);
+ }
+ if (bold) {
+ mTextPaint.setFakeBoldText(false);
+ }
+ if (underline) {
+ mTextPaint.setUnderlineText(false);
+ }
+ }
+ }
+
+ public int getCharacterHeight() {
+ return mCharHeight;
+ }
+
+ public float getCharacterWidth() {
+ return mCharWidth;
+ }
+
+ public int getTopMargin() {
+ return mCharDescent;
+ }
+
+ private Paint mTextPaint;
+ private float mCharWidth;
+ private int mCharHeight;
+ private int mCharAscent;
+ private int mCharDescent;
+ private static final char[] EXAMPLE_CHAR = {'X'};
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/Screen.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/Screen.java
new file mode 100644
index 000000000..84110beac
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/Screen.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.emulatorview;
+
+/**
+ * An abstract screen interface. A terminal screen stores lines of text. (The
+ * reason to abstract it is to allow different implementations, and to hide
+ * implementation details from clients.)
+ */
+interface Screen {
+
+ /**
+ * Set line wrap flag for a given row. Affects how lines are logically
+ * wrapped when changing screen size or converting to a transcript.
+ */
+ void setLineWrap(int row);
+
+ /**
+ * Store a Unicode code point into the screen at location (x, y)
+ *
+ * @param x X coordinate (also known as column)
+ * @param y Y coordinate (also known as row)
+ * @param codePoint Unicode code point to store
+ * @param style the text style
+ */
+ void set(int x, int y, int codePoint, int style);
+
+ /**
+ * Store byte b into the screen at location (x, y)
+ *
+ * @param x X coordinate (also known as column)
+ * @param y Y coordinate (also known as row)
+ * @param b ASCII character to store
+ * @param style the text style
+ */
+ void set(int x, int y, byte b, int style);
+
+ /**
+ * Scroll the screen down one line. To scroll the whole screen of a 24 line
+ * screen, the arguments would be (0, 24).
+ *
+ * @param topMargin First line that is scrolled.
+ * @param bottomMargin One line after the last line that is scrolled.
+ * @param style the style for the newly exposed line.
+ */
+ void scroll(int topMargin, int bottomMargin, int style);
+
+ /**
+ * Block copy characters from one position in the screen to another. The two
+ * positions can overlap. All characters of the source and destination must
+ * be within the bounds of the screen, or else an InvalidParemeterException
+ * will be thrown.
+ *
+ * @param sx source X coordinate
+ * @param sy source Y coordinate
+ * @param w width
+ * @param h height
+ * @param dx destination X coordinate
+ * @param dy destination Y coordinate
+ */
+ void blockCopy(int sx, int sy, int w, int h, int dx, int dy);
+
+ /**
+ * Block set characters. All characters must be within the bounds of the
+ * screen, or else and InvalidParemeterException will be thrown. Typically
+ * this is called with a "val" argument of 32 to clear a block of
+ * characters.
+ *
+ * @param sx source X
+ * @param sy source Y
+ * @param w width
+ * @param h height
+ * @param val value to set.
+ * @param style the text style
+ */
+ void blockSet(int sx, int sy, int w, int h, int val, int style);
+
+ /**
+ * Get the contents of the transcript buffer as a text string.
+ *
+ * @return the contents of the transcript buffer.
+ */
+ String getTranscriptText();
+
+ /**
+ * Get the contents of the transcript buffer as a text string with color
+ * information.
+ *
+ * @param colors A GrowableIntArray which will hold the colors.
+ * @return the contents of the transcript buffer.
+ */
+ String getTranscriptText(GrowableIntArray colors);
+
+ /**
+ * Get the selected text inside transcript buffer as a text string.
+ * @param x1 Selection start
+ * @param y1 Selection start
+ * @param x2 Selection end
+ * @param y2 Selection end
+ * @return the contents of the transcript buffer.
+ */
+ String getSelectedText(int x1, int y1, int x2, int y2);
+
+ /**
+ * Get the selected text inside transcript buffer as a text string with
+ * color information.
+ *
+ * @param colors A StringBuilder which will hold the colors.
+ * @param x1 Selection start
+ * @param y1 Selection start
+ * @param x2 Selection end
+ * @param y2 Selection end
+ * @return the contents of the transcript buffer.
+ */
+ String getSelectedText(GrowableIntArray colors, int x1, int y1, int x2, int y2);
+
+ /**
+ * Get the number of "active" (in-use) screen rows, including any rows in a
+ * scrollback buffer.
+ */
+ int getActiveRows();
+
+ /**
+ * Try to resize the screen without losing its contents.
+ *
+ * @param columns
+ * @param rows
+ * @param cursor An int[2] containing the current cursor position
+ * { col, row }. If the resize succeeds, the array will be
+ * updated to reflect the new location.
+ * @return Whether the resize succeeded. If the operation fails, save the
+ * contents of the screen and then use the standard resize.
+ */
+ boolean fastResize(int columns, int rows, int[] cursor);
+
+ /**
+ * Resize the screen
+ * @param columns
+ * @param rows
+ * @param style
+ */
+ void resize(int columns, int rows, int style);
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/StyleRow.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/StyleRow.java
new file mode 100644
index 000000000..de9484a7f
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/StyleRow.java
@@ -0,0 +1,97 @@
+package jackpal.androidterm.emulatorview;
+
+/**
+ * Utility class for dealing with text style lines.
+ *
+ * We pack color and formatting information for a particular character into an
+ * int -- see the TextStyle class for details. The simplest way of storing
+ * that information for a screen row would be to use an array of int -- but
+ * given that we only use the lower three bytes of the int to store information,
+ * that effectively wastes one byte per character -- nearly 8 KB per 100 lines
+ * with an 80-column transcript.
+ *
+ * Instead, we use an array of bytes and store the bytes of each int
+ * consecutively in big-endian order.
+ */
+final class StyleRow {
+ private int mStyle;
+ private int mColumns;
+ /** Initially null, will be allocated when needed. */
+ private byte[] mData;
+
+ StyleRow(int style, int columns) {
+ mStyle = style;
+ mColumns = columns;
+ }
+
+ void set(int column, int style) {
+ if (style == mStyle && mData == null) {
+ return;
+ }
+ ensureData();
+ setStyle(column, style);
+ }
+
+ int get(int column) {
+ if (mData == null) {
+ return mStyle;
+ }
+ return getStyle(column);
+ }
+
+ boolean isSolidStyle() {
+ return mData == null;
+ }
+
+ int getSolidStyle() {
+ if (mData != null) {
+ throw new IllegalArgumentException("Not a solid style");
+ }
+ return mStyle;
+ }
+
+ void copy(int start, StyleRow dst, int offset, int len) {
+ // fast case
+ if (mData == null && dst.mData == null && start == 0 && offset == 0
+ && len == mColumns) {
+ dst.mStyle = mStyle;
+ return;
+ }
+ // There are other potentially fast cases, but let's just treat them
+ // all the same for simplicity.
+ ensureData();
+ dst.ensureData();
+ System.arraycopy(mData, 3*start, dst.mData, 3*offset, 3*len);
+
+ }
+
+ void ensureData() {
+ if (mData == null) {
+ allocate();
+ }
+ }
+
+ private void allocate() {
+ mData = new byte[3*mColumns];
+ for (int i = 0; i < mColumns; i++) {
+ setStyle(i, mStyle);
+ }
+ }
+
+ private int getStyle(int column) {
+ int index = 3 * column;
+ byte[] line = mData;
+ return line[index] & 0xff | (line[index+1] & 0xff) << 8
+ | (line[index+2] & 0xff) << 16;
+ }
+
+ private void setStyle(int column, int value) {
+ int index = 3 * column;
+ byte[] line = mData;
+ line[index] = (byte) (value & 0xff);
+ line[index+1] = (byte) ((value >> 8) & 0xff);
+ line[index+2] = (byte) ((value >> 16) & 0xff);
+ }
+
+
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermKeyListener.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermKeyListener.java
new file mode 100644
index 000000000..eb2ae6099
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermKeyListener.java
@@ -0,0 +1,750 @@
+package jackpal.androidterm.emulatorview;
+
+import jackpal.androidterm.emulatorview.compat.AndroidCompat;
+import jackpal.androidterm.emulatorview.compat.KeyCharacterMapCompat;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.HashMap;
+
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import static jackpal.androidterm.emulatorview.compat.KeycodeConstants.*;
+
+/**
+ * An ASCII key listener. Supports control characters and escape. Keeps track of
+ * the current state of the alt, shift, fn, and control keys.
+ *
+ */
+class TermKeyListener {
+ private final static String TAG = "TermKeyListener";
+ private static final boolean LOG_MISC = false;
+ private static final boolean LOG_KEYS = false;
+ private static final boolean LOG_COMBINING_ACCENT = false;
+
+ /** Disabled for now because it interferes with ALT processing on phones with physical keyboards. */
+ private final static boolean SUPPORT_8_BIT_META = false;
+
+ private static final int KEYMOD_ALT = 0x80000000;
+ private static final int KEYMOD_CTRL = 0x40000000;
+ private static final int KEYMOD_SHIFT = 0x20000000;
+ /** Means this maps raw scancode */
+ private static final int KEYMOD_SCAN = 0x10000000;
+
+ private static Map mKeyMap;
+
+ private String[] mKeyCodes = new String[256];
+ private String[] mAppKeyCodes = new String[256];
+
+ private void initKeyCodes() {
+ mKeyMap = new HashMap();
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;2D");
+ mKeyMap.put(KEYMOD_ALT | KEYCODE_DPAD_LEFT, "\033[1;3D");
+ mKeyMap.put(KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;4D");
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_DPAD_LEFT, "\033[1;5D");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;6D");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYCODE_DPAD_LEFT, "\033[1;7D");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;8D");
+
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT, "\033[1;2C");
+ mKeyMap.put(KEYMOD_ALT | KEYCODE_DPAD_RIGHT, "\033[1;3C");
+ mKeyMap.put(KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT, "\033[1;4C");
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_DPAD_RIGHT, "\033[1;5C");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT, "\033[1;6C");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYCODE_DPAD_RIGHT, "\033[1;7C");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT, "\033[1;8C");
+
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_DPAD_UP, "\033[1;2A");
+ mKeyMap.put(KEYMOD_ALT | KEYCODE_DPAD_UP, "\033[1;3A");
+ mKeyMap.put(KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_UP, "\033[1;4A");
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_DPAD_UP, "\033[1;5A");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_SHIFT | KEYCODE_DPAD_UP, "\033[1;6A");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYCODE_DPAD_UP, "\033[1;7A");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_UP, "\033[1;8A");
+
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_DPAD_DOWN, "\033[1;2B");
+ mKeyMap.put(KEYMOD_ALT | KEYCODE_DPAD_DOWN, "\033[1;3B");
+ mKeyMap.put(KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_DOWN, "\033[1;4B");
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_DPAD_DOWN, "\033[1;5B");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_SHIFT | KEYCODE_DPAD_DOWN, "\033[1;6B");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYCODE_DPAD_DOWN, "\033[1;7B");
+ mKeyMap.put(KEYMOD_CTRL | KEYMOD_ALT | KEYMOD_SHIFT | KEYCODE_DPAD_DOWN, "\033[1;8B");
+
+ //^[[3~
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_FORWARD_DEL, "\033[3;2~");
+ mKeyMap.put(KEYMOD_ALT | KEYCODE_FORWARD_DEL, "\033[3;3~");
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_FORWARD_DEL, "\033[3;5~");
+
+ //^[[2~
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_INSERT, "\033[2;2~");
+ mKeyMap.put(KEYMOD_ALT | KEYCODE_INSERT, "\033[2;3~");
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_INSERT, "\033[2;5~");
+
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_MOVE_HOME, "\033[1;5H");
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_MOVE_END, "\033[1;5F");
+
+ mKeyMap.put(KEYMOD_ALT | KEYCODE_ENTER, "\033\r");
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_ENTER, "\n");
+ // Duh, so special...
+ mKeyMap.put(KEYMOD_CTRL | KEYCODE_SPACE, "\000");
+
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F1, "\033[1;2P");
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F2, "\033[1;2Q");
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F3, "\033[1;2R");
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F4, "\033[1;2S");
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F5, "\033[15;2~");
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F6, "\033[17;2~");
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F7, "\033[18;2~");
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F8, "\033[19;2~");
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F9, "\033[20;2~");
+ mKeyMap.put(KEYMOD_SHIFT | KEYCODE_F10, "\033[21;2~");
+
+ mKeyCodes[KEYCODE_DPAD_CENTER] = "\015";
+ mKeyCodes[KEYCODE_DPAD_UP] = "\033[A";
+ mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B";
+ mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C";
+ mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D";
+ setFnKeys("vt100");
+ mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print
+ // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~";
+ mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break
+
+ mKeyCodes[KEYCODE_TAB] = "\011";
+ mKeyCodes[KEYCODE_ENTER] = "\015";
+ mKeyCodes[KEYCODE_ESCAPE] = "\033";
+
+ mKeyCodes[KEYCODE_INSERT] = "\033[2~";
+ mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~";
+ // Home/End keys are set by setFnKeys()
+ mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~";
+ mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~";
+ mKeyCodes[KEYCODE_DEL]= "\177";
+ mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP";
+ mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/";
+ mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*";
+ mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-";
+ mKeyCodes[KEYCODE_NUMPAD_ADD] = "+";
+ mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015";
+ mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "=";
+ mKeyCodes[KEYCODE_NUMPAD_COMMA] = ",";
+/*
+ mKeyCodes[KEYCODE_NUMPAD_DOT] = ".";
+ mKeyCodes[KEYCODE_NUMPAD_0] = "0";
+ mKeyCodes[KEYCODE_NUMPAD_1] = "1";
+ mKeyCodes[KEYCODE_NUMPAD_2] = "2";
+ mKeyCodes[KEYCODE_NUMPAD_3] = "3";
+ mKeyCodes[KEYCODE_NUMPAD_4] = "4";
+ mKeyCodes[KEYCODE_NUMPAD_5] = "5";
+ mKeyCodes[KEYCODE_NUMPAD_6] = "6";
+ mKeyCodes[KEYCODE_NUMPAD_7] = "7";
+ mKeyCodes[KEYCODE_NUMPAD_8] = "8";
+ mKeyCodes[KEYCODE_NUMPAD_9] = "9";
+*/
+ // Keypad is used for cursor/func keys
+ mKeyCodes[KEYCODE_NUMPAD_DOT] = mKeyCodes[KEYCODE_FORWARD_DEL];
+ mKeyCodes[KEYCODE_NUMPAD_0] = mKeyCodes[KEYCODE_INSERT];
+ mKeyCodes[KEYCODE_NUMPAD_1] = mKeyCodes[KEYCODE_MOVE_END];
+ mKeyCodes[KEYCODE_NUMPAD_2] = mKeyCodes[KEYCODE_DPAD_DOWN];
+ mKeyCodes[KEYCODE_NUMPAD_3] = mKeyCodes[KEYCODE_PAGE_DOWN];
+ mKeyCodes[KEYCODE_NUMPAD_4] = mKeyCodes[KEYCODE_DPAD_LEFT];
+ mKeyCodes[KEYCODE_NUMPAD_5] = "5";
+ mKeyCodes[KEYCODE_NUMPAD_6] = mKeyCodes[KEYCODE_DPAD_RIGHT];
+ mKeyCodes[KEYCODE_NUMPAD_7] = mKeyCodes[KEYCODE_MOVE_HOME];
+ mKeyCodes[KEYCODE_NUMPAD_8] = mKeyCodes[KEYCODE_DPAD_UP];
+ mKeyCodes[KEYCODE_NUMPAD_9] = mKeyCodes[KEYCODE_PAGE_UP];
+
+
+// mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA";
+// mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB";
+// mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC";
+// mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD";
+ mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo";
+ mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj";
+ mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om";
+ mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok";
+ mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM";
+ mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX";
+ mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On";
+ mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol";
+ mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op";
+ mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq";
+ mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or";
+ mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os";
+ mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot";
+ mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou";
+ mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov";
+ mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow";
+ mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox";
+ mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy";
+ }
+
+ public void setCursorKeysApplicationMode(boolean val) {
+ if (LOG_MISC) {
+ Log.d(EmulatorDebug.LOG_TAG, "CursorKeysApplicationMode=" + val);
+ }
+ if (val) {
+ mKeyCodes[KEYCODE_NUMPAD_8] = mKeyCodes[KEYCODE_DPAD_UP] = "\033OA";
+ mKeyCodes[KEYCODE_NUMPAD_2] = mKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB";
+ mKeyCodes[KEYCODE_NUMPAD_6] = mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC";
+ mKeyCodes[KEYCODE_NUMPAD_4] = mKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD";
+ } else {
+ mKeyCodes[KEYCODE_NUMPAD_8] = mKeyCodes[KEYCODE_DPAD_UP] = "\033[A";
+ mKeyCodes[KEYCODE_NUMPAD_2] = mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B";
+ mKeyCodes[KEYCODE_NUMPAD_6] = mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C";
+ mKeyCodes[KEYCODE_NUMPAD_4] = mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D";
+ }
+ }
+
+ /**
+ * The state engine for a modifier key. Can be pressed, released, locked,
+ * and so on.
+ *
+ */
+ private class ModifierKey {
+
+ private int mState;
+
+ private static final int UNPRESSED = 0;
+
+ private static final int PRESSED = 1;
+
+ private static final int RELEASED = 2;
+
+ private static final int USED = 3;
+
+ private static final int LOCKED = 4;
+
+ /**
+ * Construct a modifier key. UNPRESSED by default.
+ *
+ */
+ public ModifierKey() {
+ mState = UNPRESSED;
+ }
+
+ public void onPress() {
+ switch (mState) {
+ case PRESSED:
+ // This is a repeat before use
+ break;
+ case RELEASED:
+ mState = LOCKED;
+ break;
+ case USED:
+ // This is a repeat after use
+ break;
+ case LOCKED:
+ mState = UNPRESSED;
+ break;
+ default:
+ mState = PRESSED;
+ break;
+ }
+ }
+
+ public void onRelease() {
+ switch (mState) {
+ case USED:
+ mState = UNPRESSED;
+ break;
+ case PRESSED:
+ mState = RELEASED;
+ break;
+ default:
+ // Leave state alone
+ break;
+ }
+ }
+
+ public void adjustAfterKeypress() {
+ switch (mState) {
+ case PRESSED:
+ mState = USED;
+ break;
+ case RELEASED:
+ mState = UNPRESSED;
+ break;
+ default:
+ // Leave state alone
+ break;
+ }
+ }
+
+ public boolean isActive() {
+ return mState != UNPRESSED;
+ }
+
+ public int getUIMode() {
+ switch (mState) {
+ default:
+ case UNPRESSED:
+ return TextRenderer.MODE_OFF;
+ case PRESSED:
+ case RELEASED:
+ case USED:
+ return TextRenderer.MODE_ON;
+ case LOCKED:
+ return TextRenderer.MODE_LOCKED;
+ }
+ }
+ }
+
+ private ModifierKey mAltKey = new ModifierKey();
+
+ private ModifierKey mCapKey = new ModifierKey();
+
+ private ModifierKey mControlKey = new ModifierKey();
+
+ private ModifierKey mFnKey = new ModifierKey();
+
+ private int mCursorMode;
+
+ private boolean mHardwareControlKey;
+
+ private TermSession mTermSession;
+
+ private int mBackKeyCode;
+ private boolean mAltSendsEsc;
+
+ private int mCombiningAccent;
+
+ // Map keycodes out of (above) the Unicode code point space.
+ static public final int KEYCODE_OFFSET = 0xA00000;
+
+ /**
+ * Construct a term key listener.
+ *
+ */
+ public TermKeyListener(TermSession termSession) {
+ mTermSession = termSession;
+ initKeyCodes();
+ updateCursorMode();
+ }
+
+ public void setBackKeyCharacter(int code) {
+ mBackKeyCode = code;
+ }
+
+ public void setAltSendsEsc(boolean flag) {
+ mAltSendsEsc = flag;
+ }
+
+ public void handleHardwareControlKey(boolean down) {
+ mHardwareControlKey = down;
+ }
+
+ public void onPause() {
+ // Ensure we don't have any left-over modifier state when switching
+ // views.
+ mHardwareControlKey = false;
+ }
+
+ public void onResume() {
+ // Nothing special.
+ }
+
+ public void handleControlKey(boolean down) {
+ if (down) {
+ mControlKey.onPress();
+ } else {
+ mControlKey.onRelease();
+ }
+ updateCursorMode();
+ }
+
+ public void handleFnKey(boolean down) {
+ if (down) {
+ mFnKey.onPress();
+ } else {
+ mFnKey.onRelease();
+ }
+ updateCursorMode();
+ }
+
+ public void setTermType(String termType) {
+ setFnKeys(termType);
+ }
+
+ private void setFnKeys(String termType) {
+ // These key assignments taken from the debian squeeze terminfo database.
+ if (termType.equals("xterm")) {
+ mKeyCodes[KEYCODE_NUMPAD_7] = mKeyCodes[KEYCODE_MOVE_HOME] = "\033OH";
+ mKeyCodes[KEYCODE_NUMPAD_1] = mKeyCodes[KEYCODE_MOVE_END] = "\033OF";
+ } else {
+ mKeyCodes[KEYCODE_NUMPAD_7] = mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~";
+ mKeyCodes[KEYCODE_NUMPAD_1] = mKeyCodes[KEYCODE_MOVE_END] = "\033[4~";
+ }
+ if (termType.equals("vt100")) {
+ mKeyCodes[KEYCODE_F1] = "\033OP"; // VT100 PF1
+ mKeyCodes[KEYCODE_F2] = "\033OQ"; // VT100 PF2
+ mKeyCodes[KEYCODE_F3] = "\033OR"; // VT100 PF3
+ mKeyCodes[KEYCODE_F4] = "\033OS"; // VT100 PF4
+ // the following keys are in the database, but aren't on a real vt100.
+ mKeyCodes[KEYCODE_F5] = "\033Ot";
+ mKeyCodes[KEYCODE_F6] = "\033Ou";
+ mKeyCodes[KEYCODE_F7] = "\033Ov";
+ mKeyCodes[KEYCODE_F8] = "\033Ol";
+ mKeyCodes[KEYCODE_F9] = "\033Ow";
+ mKeyCodes[KEYCODE_F10] = "\033Ox";
+ // The following keys are not in database.
+ mKeyCodes[KEYCODE_F11] = "\033[23~";
+ mKeyCodes[KEYCODE_F12] = "\033[24~";
+ } else if (termType.startsWith("linux")) {
+ mKeyCodes[KEYCODE_F1] = "\033[[A";
+ mKeyCodes[KEYCODE_F2] = "\033[[B";
+ mKeyCodes[KEYCODE_F3] = "\033[[C";
+ mKeyCodes[KEYCODE_F4] = "\033[[D";
+ mKeyCodes[KEYCODE_F5] = "\033[[E";
+ mKeyCodes[KEYCODE_F6] = "\033[17~";
+ mKeyCodes[KEYCODE_F7] = "\033[18~";
+ mKeyCodes[KEYCODE_F8] = "\033[19~";
+ mKeyCodes[KEYCODE_F9] = "\033[20~";
+ mKeyCodes[KEYCODE_F10] = "\033[21~";
+ mKeyCodes[KEYCODE_F11] = "\033[23~";
+ mKeyCodes[KEYCODE_F12] = "\033[24~";
+ } else {
+ // default
+ // screen, screen-256colors, xterm, anything new
+ mKeyCodes[KEYCODE_F1] = "\033OP"; // VT100 PF1
+ mKeyCodes[KEYCODE_F2] = "\033OQ"; // VT100 PF2
+ mKeyCodes[KEYCODE_F3] = "\033OR"; // VT100 PF3
+ mKeyCodes[KEYCODE_F4] = "\033OS"; // VT100 PF4
+ mKeyCodes[KEYCODE_F5] = "\033[15~";
+ mKeyCodes[KEYCODE_F6] = "\033[17~";
+ mKeyCodes[KEYCODE_F7] = "\033[18~";
+ mKeyCodes[KEYCODE_F8] = "\033[19~";
+ mKeyCodes[KEYCODE_F9] = "\033[20~";
+ mKeyCodes[KEYCODE_F10] = "\033[21~";
+ mKeyCodes[KEYCODE_F11] = "\033[23~";
+ mKeyCodes[KEYCODE_F12] = "\033[24~";
+ }
+ }
+
+ public int mapControlChar(int ch) {
+ return mapControlChar(mHardwareControlKey || mControlKey.isActive(), mFnKey.isActive(), ch);
+ }
+
+ public int mapControlChar(boolean control, boolean fn, int ch) {
+ int result = ch;
+ if (control) {
+ // Search is the control key.
+ if (result >= 'a' && result <= 'z') {
+ result = (char) (result - 'a' + '\001');
+ } else if (result >= 'A' && result <= 'Z') {
+ result = (char) (result - 'A' + '\001');
+ } else if (result == ' ' || result == '2') {
+ result = 0;
+ } else if (result == '[' || result == '3') {
+ result = 27; // ^[ (Esc)
+ } else if (result == '\\' || result == '4') {
+ result = 28;
+ } else if (result == ']' || result == '5') {
+ result = 29;
+ } else if (result == '^' || result == '6') {
+ result = 30; // control-^
+ } else if (result == '_' || result == '7') {
+ result = 31;
+ } else if (result == '8') {
+ result = 127; // DEL
+ } else if (result == '9') {
+ result = KEYCODE_OFFSET + KEYCODE_F11;
+ } else if (result == '0') {
+ result = KEYCODE_OFFSET + KEYCODE_F12;
+ }
+ } else if (fn) {
+ if (result == 'w' || result == 'W') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP;
+ } else if (result == 'a' || result == 'A') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT;
+ } else if (result == 's' || result == 'S') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN;
+ } else if (result == 'd' || result == 'D') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT;
+ } else if (result == 'p' || result == 'P') {
+ result = KEYCODE_OFFSET + KEYCODE_PAGE_UP;
+ } else if (result == 'n' || result == 'N') {
+ result = KEYCODE_OFFSET + KEYCODE_PAGE_DOWN;
+ } else if (result == 't' || result == 'T') {
+ result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB;
+ } else if (result == 'l' || result == 'L') {
+ result = '|';
+ } else if (result == 'u' || result == 'U') {
+ result = '_';
+ } else if (result == 'e' || result == 'E') {
+ result = 27; // ^[ (Esc)
+ } else if (result == '.') {
+ result = 28; // ^\
+ } else if (result > '0' && result <= '9') {
+ // F1-F9
+ result = (char)(result + KEYCODE_OFFSET + KEYCODE_F1 - 1);
+ } else if (result == '0') {
+ result = KEYCODE_OFFSET + KEYCODE_F10;
+ } else if (result == 'i' || result == 'I') {
+ result = KEYCODE_OFFSET + KEYCODE_INSERT;
+ } else if (result == 'x' || result == 'X') {
+ result = KEYCODE_OFFSET + KEYCODE_FORWARD_DEL;
+ } else if (result == 'h' || result == 'H') {
+ result = KEYCODE_OFFSET + KEYCODE_MOVE_HOME;
+ } else if (result == 'f' || result == 'F') {
+ result = KEYCODE_OFFSET + KEYCODE_MOVE_END;
+ }
+ }
+
+ if (result > -1) {
+ mAltKey.adjustAfterKeypress();
+ mCapKey.adjustAfterKeypress();
+ mControlKey.adjustAfterKeypress();
+ mFnKey.adjustAfterKeypress();
+ updateCursorMode();
+ }
+
+ return result;
+ }
+
+ /**
+ * Handle a keyDown event.
+ *
+ * @param keyCode the keycode of the keyDown event
+ *
+ */
+ public void keyDown(int keyCode, KeyEvent event, boolean appMode,
+ boolean allowToggle) throws IOException {
+ if (LOG_KEYS) {
+ Log.i(TAG, "keyDown(" + keyCode + "," + event + "," + appMode + "," + allowToggle + ")");
+ }
+ if (handleKeyCode(keyCode, event, appMode)) {
+ return;
+ }
+ int result = -1;
+ boolean chordedCtrl = false;
+ boolean setHighBit = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ALT_RIGHT:
+ case KeyEvent.KEYCODE_ALT_LEFT:
+ if (allowToggle) {
+ mAltKey.onPress();
+ updateCursorMode();
+ }
+ break;
+
+ case KeyEvent.KEYCODE_SHIFT_LEFT:
+ case KeyEvent.KEYCODE_SHIFT_RIGHT:
+ if (allowToggle) {
+ mCapKey.onPress();
+ updateCursorMode();
+ }
+ break;
+
+ case KEYCODE_CTRL_LEFT:
+ case KEYCODE_CTRL_RIGHT:
+ // Ignore the control key.
+ return;
+
+ case KEYCODE_CAPS_LOCK:
+ // Ignore the capslock key.
+ return;
+
+ case KEYCODE_FUNCTION:
+ // Ignore the function key.
+ return;
+
+ case KeyEvent.KEYCODE_BACK:
+ result = mBackKeyCode;
+ break;
+
+ default: {
+ int metaState = event.getMetaState();
+ chordedCtrl = ((META_CTRL_ON & metaState) != 0);
+ boolean effectiveCaps = allowToggle &&
+ (mCapKey.isActive());
+ boolean effectiveAlt = allowToggle && mAltKey.isActive();
+ int effectiveMetaState = metaState & (~META_CTRL_MASK);
+ if (effectiveCaps) {
+ effectiveMetaState |= KeyEvent.META_SHIFT_ON;
+ }
+ if (!allowToggle && (effectiveMetaState & META_ALT_ON) != 0) {
+ effectiveAlt = true;
+ }
+ if (effectiveAlt) {
+ if (mAltSendsEsc) {
+ mTermSession.write(new byte[]{0x1b},0,1);
+ effectiveMetaState &= ~KeyEvent.META_ALT_MASK;
+ } else if (SUPPORT_8_BIT_META) {
+ setHighBit = true;
+ effectiveMetaState &= ~KeyEvent.META_ALT_MASK;
+ } else {
+ // Legacy behavior: Pass Alt through to allow composing characters.
+ effectiveMetaState |= KeyEvent.META_ALT_ON;
+ }
+ }
+
+ // Note: The Hacker keyboard IME key labeled Alt actually sends Meta.
+
+
+ if ((metaState & KeyEvent.META_META_ON) != 0) {
+ if (mAltSendsEsc) {
+ mTermSession.write(new byte[]{0x1b},0,1);
+ effectiveMetaState &= ~KeyEvent.META_META_MASK;
+ } else {
+ if (SUPPORT_8_BIT_META) {
+ setHighBit = true;
+ effectiveMetaState &= ~KeyEvent.META_META_MASK;
+ }
+ }
+ }
+ result = event.getUnicodeChar(effectiveMetaState);
+
+ if ((result & KeyCharacterMap.COMBINING_ACCENT) != 0) {
+ if (LOG_COMBINING_ACCENT) {
+ Log.i(TAG, "Got combining accent " + result);
+ }
+ mCombiningAccent = result & KeyCharacterMap.COMBINING_ACCENT_MASK;
+ return;
+ }
+ if (mCombiningAccent != 0) {
+ int unaccentedChar = result;
+ result = KeyCharacterMap.getDeadChar(mCombiningAccent, unaccentedChar);
+ if (LOG_COMBINING_ACCENT) {
+ Log.i(TAG, "getDeadChar(" + mCombiningAccent + ", " + unaccentedChar + ") -> " + result);
+ }
+ mCombiningAccent = 0;
+ }
+
+ break;
+ }
+ }
+
+ boolean effectiveControl = chordedCtrl || mHardwareControlKey || (allowToggle && mControlKey.isActive());
+ boolean effectiveFn = allowToggle && mFnKey.isActive();
+
+ result = mapControlChar(effectiveControl, effectiveFn, result);
+
+ if (result >= KEYCODE_OFFSET) {
+ handleKeyCode(result - KEYCODE_OFFSET, null, appMode);
+ } else if (result >= 0) {
+ if (setHighBit) {
+ result |= 0x80;
+ }
+ mTermSession.write(result);
+ }
+ }
+
+ public int getCombiningAccent() {
+ return mCombiningAccent;
+ }
+
+ public int getCursorMode() {
+ return mCursorMode;
+ }
+
+ private void updateCursorMode() {
+ mCursorMode = getCursorModeHelper(mCapKey, TextRenderer.MODE_SHIFT_SHIFT)
+ | getCursorModeHelper(mAltKey, TextRenderer.MODE_ALT_SHIFT)
+ | getCursorModeHelper(mControlKey, TextRenderer.MODE_CTRL_SHIFT)
+ | getCursorModeHelper(mFnKey, TextRenderer.MODE_FN_SHIFT);
+ }
+
+ private static int getCursorModeHelper(ModifierKey key, int shift) {
+ return key.getUIMode() << shift;
+ }
+
+ static boolean isEventFromToggleDevice(KeyEvent event) {
+ if (AndroidCompat.SDK < 11) {
+ return true;
+ }
+ KeyCharacterMapCompat kcm = KeyCharacterMapCompat.wrap(
+ KeyCharacterMap.load(event.getDeviceId()));
+ return kcm.getModifierBehaviour() ==
+ KeyCharacterMapCompat.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED;
+ }
+
+ public boolean handleKeyCode(int keyCode, KeyEvent event, boolean appMode) throws IOException {
+ String code = null;
+ if (event != null) {
+ int keyMod = 0;
+ // META_CTRL_ON was added only in API 11, so don't use it,
+ // use our own tracking of Ctrl key instead.
+ // (event.getMetaState() & META_CTRL_ON) != 0
+ if (mHardwareControlKey || mControlKey.isActive()) {
+ keyMod |= KEYMOD_CTRL;
+ }
+ if ((event.getMetaState() & META_ALT_ON) != 0) {
+ keyMod |= KEYMOD_ALT;
+ }
+ if ((event.getMetaState() & META_SHIFT_ON) != 0) {
+ keyMod |= KEYMOD_SHIFT;
+ }
+ // First try to map scancode
+ code = mKeyMap.get(event.getScanCode() | KEYMOD_SCAN | keyMod);
+ if (code == null) {
+ code = mKeyMap.get(keyCode | keyMod);
+ }
+ }
+
+ if (code == null && keyCode >= 0 && keyCode < mKeyCodes.length) {
+ if (appMode) {
+ code = mAppKeyCodes[keyCode];
+ }
+ if (code == null) {
+ code = mKeyCodes[keyCode];
+ }
+ }
+
+ if (code != null) {
+ if (EmulatorDebug.LOG_CHARACTERS_FLAG) {
+ byte[] bytes = code.getBytes();
+ Log.d(EmulatorDebug.LOG_TAG, "Out: '" + EmulatorDebug.bytesToString(bytes, 0, bytes.length) + "'");
+ }
+ mTermSession.write(code);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle a keyUp event.
+ *
+ * @param keyCode the keyCode of the keyUp event
+ */
+ public void keyUp(int keyCode, KeyEvent event) {
+ boolean allowToggle = isEventFromToggleDevice(event);
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ALT_LEFT:
+ case KeyEvent.KEYCODE_ALT_RIGHT:
+ if (allowToggle) {
+ mAltKey.onRelease();
+ updateCursorMode();
+ }
+ break;
+ case KeyEvent.KEYCODE_SHIFT_LEFT:
+ case KeyEvent.KEYCODE_SHIFT_RIGHT:
+ if (allowToggle) {
+ mCapKey.onRelease();
+ updateCursorMode();
+ }
+ break;
+
+ case KEYCODE_CTRL_LEFT:
+ case KEYCODE_CTRL_RIGHT:
+ // ignore control keys.
+ break;
+
+ default:
+ // Ignore other keyUps
+ break;
+ }
+ }
+
+ public boolean getAltSendsEsc() {
+ return mAltSendsEsc;
+ }
+
+ public boolean isAltActive() {
+ return mAltKey.isActive();
+ }
+
+ public boolean isCtrlActive() {
+ return mControlKey.isActive();
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermSession.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermSession.java
new file mode 100644
index 000000000..4d588e538
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermSession.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.emulatorview;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * A terminal session, consisting of a VT100 terminal emulator and its
+ * input and output streams.
+ *
+ * You need to supply an {@link InputStream} and {@link OutputStream} to
+ * provide input and output to the terminal. For a locally running
+ * program, these would typically point to a tty; for a telnet program
+ * they might point to a network socket. Reader and writer threads will be
+ * spawned to do I/O to these streams. All other operations, including
+ * processing of input and output in {@link #processInput processInput} and
+ * {@link #write(byte[], int, int) write}, will be performed on the main thread.
+ *
+ * Call {@link #setTermIn} and {@link #setTermOut} to connect the input and
+ * output streams to the emulator. When all of your initialization is
+ * complete, your initial screen size is known, and you're ready to
+ * start VT100 emulation, call {@link #initializeEmulator} or {@link
+ * #updateSize} with the number of rows and columns the terminal should
+ * initially have. (If you attach the session to an {@link EmulatorView},
+ * the view will take care of setting the screen size and initializing the
+ * emulator for you.)
+ *
+ * When you're done with the session, you should call {@link #finish} on it.
+ * This frees emulator data from memory, stops the reader and writer threads,
+ * and closes the attached I/O streams.
+ */
+public class TermSession {
+ public void setKeyListener(TermKeyListener l) {
+ mKeyListener = l;
+ }
+
+ private TermKeyListener mKeyListener;
+
+ private ColorScheme mColorScheme = BaseTextRenderer.defaultColorScheme;
+ private UpdateCallback mNotify;
+
+ private OutputStream mTermOut;
+ private InputStream mTermIn;
+
+ private String mTitle;
+
+ private TranscriptScreen mTranscriptScreen;
+ private TerminalEmulator mEmulator;
+
+ private boolean mDefaultUTF8Mode;
+
+ private Thread mReaderThread;
+ private ByteQueue mByteQueue;
+ private byte[] mReceiveBuffer;
+
+ private Thread mWriterThread;
+ private ByteQueue mWriteQueue;
+ private Handler mWriterHandler;
+
+ private CharBuffer mWriteCharBuffer;
+ private ByteBuffer mWriteByteBuffer;
+ private CharsetEncoder mUTF8Encoder;
+
+ // Number of rows in the transcript
+ private static final int TRANSCRIPT_ROWS = 10000;
+
+ private static final int NEW_INPUT = 1;
+ private static final int NEW_OUTPUT = 2;
+ private static final int FINISH = 3;
+ private static final int EOF = 4;
+
+ /**
+ * Callback to be invoked when a {@link TermSession} finishes.
+ *
+ * @see TermSession#setUpdateCallback
+ */
+ public interface FinishCallback {
+ /**
+ * Callback function to be invoked when a {@link TermSession} finishes.
+ *
+ * @param session The TermSession which has finished.
+ */
+ void onSessionFinish(TermSession session);
+ }
+ private FinishCallback mFinishCallback;
+
+ private boolean mIsRunning = false;
+ private Handler mMsgHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (!mIsRunning) {
+ return;
+ }
+ if (msg.what == NEW_INPUT) {
+ readFromProcess();
+ } else if (msg.what == EOF) {
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ onProcessExit();
+ }
+ });
+ }
+ }
+ };
+
+ private UpdateCallback mTitleChangedListener;
+
+ public TermSession() {
+ this(false);
+ }
+
+ public TermSession(final boolean exitOnEOF) {
+ mWriteCharBuffer = CharBuffer.allocate(2);
+ mWriteByteBuffer = ByteBuffer.allocate(4);
+ mUTF8Encoder = Charset.forName("UTF-8").newEncoder();
+ mUTF8Encoder.onMalformedInput(CodingErrorAction.REPLACE);
+ mUTF8Encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+
+ mReceiveBuffer = new byte[4 * 1024];
+ mByteQueue = new ByteQueue(4 * 1024);
+ mReaderThread = new Thread() {
+ private byte[] mBuffer = new byte[4096];
+
+ @Override
+ public void run() {
+ try {
+ while(true) {
+ int read = mTermIn.read(mBuffer);
+ if (read == -1) {
+ // EOF -- process exited
+ break;
+ }
+ int offset = 0;
+ while (read > 0) {
+ int written = mByteQueue.write(mBuffer,
+ offset, read);
+ offset += written;
+ read -= written;
+ mMsgHandler.sendMessage(
+ mMsgHandler.obtainMessage(NEW_INPUT));
+ }
+ }
+ } catch (IOException e) {
+ } catch (InterruptedException e) {
+ }
+
+ if (exitOnEOF) mMsgHandler.sendMessage(mMsgHandler.obtainMessage(EOF));
+ }
+ };
+ mReaderThread.setName("TermSession input reader");
+
+ mWriteQueue = new ByteQueue(4096);
+ mWriterThread = new Thread() {
+ private byte[] mBuffer = new byte[4096];
+
+ @Override
+ public void run() {
+ Looper.prepare();
+
+ mWriterHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == NEW_OUTPUT) {
+ writeToOutput();
+ } else if (msg.what == FINISH) {
+ Looper.myLooper().quit();
+ }
+ }
+ };
+
+ // Drain anything in the queue from before we started
+ writeToOutput();
+
+ Looper.loop();
+ }
+
+ private void writeToOutput() {
+ ByteQueue writeQueue = mWriteQueue;
+ byte[] buffer = mBuffer;
+ OutputStream termOut = mTermOut;
+
+ int bytesAvailable = writeQueue.getBytesAvailable();
+ int bytesToWrite = Math.min(bytesAvailable, buffer.length);
+
+ if (bytesToWrite == 0) {
+ return;
+ }
+
+ try {
+ writeQueue.read(buffer, 0, bytesToWrite);
+ termOut.write(buffer, 0, bytesToWrite);
+ termOut.flush();
+ } catch (IOException e) {
+ // Ignore exception
+ // We don't really care if the receiver isn't listening.
+ // We just make a best effort to answer the query.
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+ mWriterThread.setName("TermSession output writer");
+ }
+
+ protected void onProcessExit() {
+ finish();
+ }
+
+ /**
+ * Set the terminal emulator's window size and start terminal emulation.
+ *
+ * @param columns The number of columns in the terminal window.
+ * @param rows The number of rows in the terminal window.
+ */
+ public void initializeEmulator(int columns, int rows) {
+ mTranscriptScreen = new TranscriptScreen(columns, TRANSCRIPT_ROWS, rows, mColorScheme);
+ mEmulator = new TerminalEmulator(this, mTranscriptScreen, columns, rows, mColorScheme);
+ mEmulator.setDefaultUTF8Mode(mDefaultUTF8Mode);
+ mEmulator.setKeyListener(mKeyListener);
+
+ mIsRunning = true;
+ mReaderThread.start();
+ mWriterThread.start();
+ }
+
+ /**
+ * Write data to the terminal output. The written data will be consumed by
+ * the emulation client as input.
+ *
+ * write itself runs on the main thread. The default
+ * implementation writes the data into a circular buffer and signals the
+ * writer thread to copy it from there to the {@link OutputStream}.
+ *
+ * Subclasses may override this method to modify the output before writing
+ * it to the stream, but implementations in derived classes should call
+ * through to this method to do the actual writing.
+ *
+ * @param data An array of bytes to write to the terminal.
+ * @param offset The offset into the array at which the data starts.
+ * @param count The number of bytes to be written.
+ */
+ public void write(byte[] data, int offset, int count) {
+ try {
+ while (count > 0) {
+ int written = mWriteQueue.write(data, offset, count);
+ offset += written;
+ count -= written;
+ notifyNewOutput();
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+
+ /**
+ * Write the UTF-8 representation of a String to the terminal output. The
+ * written data will be consumed by the emulation client as input.
+ *
+ * This implementation encodes the String and then calls
+ * {@link #write(byte[], int, int)} to do the actual writing. It should
+ * therefore usually be unnecessary to override this method; override
+ * {@link #write(byte[], int, int)} instead.
+ *
+ * @param data The String to write to the terminal.
+ */
+ public void write(String data) {
+ try {
+ byte[] bytes = data.getBytes("UTF-8");
+ write(bytes, 0, bytes.length);
+ } catch (UnsupportedEncodingException e) {
+ }
+ }
+
+ /**
+ * Write the UTF-8 representation of a single Unicode code point to the
+ * terminal output. The written data will be consumed by the emulation
+ * client as input.
+ *
+ * This implementation encodes the code point and then calls
+ * {@link #write(byte[], int, int)} to do the actual writing. It should
+ * therefore usually be unnecessary to override this method; override
+ * {@link #write(byte[], int, int)} instead.
+ *
+ * @param codePoint The Unicode code point to write to the terminal.
+ */
+ public void write(int codePoint) {
+ ByteBuffer byteBuf = mWriteByteBuffer;
+ if (codePoint < 128) {
+ // Fast path for ASCII characters
+ byte[] buf = byteBuf.array();
+ buf[0] = (byte) codePoint;
+ write(buf, 0, 1);
+ return;
+ }
+
+ CharBuffer charBuf = mWriteCharBuffer;
+ CharsetEncoder encoder = mUTF8Encoder;
+
+ charBuf.clear();
+ byteBuf.clear();
+ Character.toChars(codePoint, charBuf.array(), 0);
+ encoder.reset();
+ encoder.encode(charBuf, byteBuf, true);
+ encoder.flush(byteBuf);
+ write(byteBuf.array(), 0, byteBuf.position()-1);
+ }
+
+ /* Notify the writer thread that there's new output waiting */
+ private void notifyNewOutput() {
+ Handler writerHandler = mWriterHandler;
+ if (writerHandler == null) {
+ /* Writer thread isn't started -- will pick up data once it does */
+ return;
+ }
+ writerHandler.sendEmptyMessage(NEW_OUTPUT);
+ }
+
+ /**
+ * Get the {@link OutputStream} associated with this session.
+ *
+ * @return This session's {@link OutputStream}.
+ */
+ public OutputStream getTermOut() {
+ return mTermOut;
+ }
+
+ /**
+ * Set the {@link OutputStream} associated with this session.
+ *
+ * @param termOut This session's {@link OutputStream}.
+ */
+ public void setTermOut(OutputStream termOut) {
+ mTermOut = termOut;
+ }
+
+ /**
+ * Get the {@link InputStream} associated with this session.
+ *
+ * @return This session's {@link InputStream}.
+ */
+ public InputStream getTermIn() {
+ return mTermIn;
+ }
+
+ /**
+ * Set the {@link InputStream} associated with this session.
+ *
+ * @param termIn This session's {@link InputStream}.
+ */
+ public void setTermIn(InputStream termIn) {
+ mTermIn = termIn;
+ }
+
+ /**
+ * @return Whether the terminal emulation is currently running.
+ */
+ public boolean isRunning() {
+ return mIsRunning;
+ }
+
+ TranscriptScreen getTranscriptScreen() {
+ return mTranscriptScreen;
+ }
+
+ TerminalEmulator getEmulator() {
+ return mEmulator;
+ }
+
+ /**
+ * Set an {@link UpdateCallback} to be invoked when the terminal emulator's
+ * screen is changed.
+ *
+ * @param notify The {@link UpdateCallback} to be invoked on changes.
+ */
+ public void setUpdateCallback(UpdateCallback notify) {
+ mNotify = notify;
+ }
+
+ /**
+ * Notify the {@link UpdateCallback} registered by {@link
+ * #setUpdateCallback setUpdateCallback} that the screen has changed.
+ */
+ protected void notifyUpdate() {
+ if (mNotify != null) {
+ mNotify.onUpdate();
+ }
+ }
+
+ /**
+ * Get the terminal session's title (may be null).
+ */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Change the terminal session's title.
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ notifyTitleChanged();
+ }
+
+ /**
+ * Set an {@link UpdateCallback} to be invoked when the terminal emulator's
+ * title is changed.
+ *
+ * @param listener The {@link UpdateCallback} to be invoked on changes.
+ */
+ public void setTitleChangedListener(UpdateCallback listener) {
+ mTitleChangedListener = listener;
+ }
+
+ /**
+ * Notify the UpdateCallback registered for title changes, if any, that the
+ * terminal session's title has changed.
+ */
+ protected void notifyTitleChanged() {
+ UpdateCallback listener = mTitleChangedListener;
+ if (listener != null) {
+ listener.onUpdate();
+ }
+ }
+
+ /**
+ * Change the terminal's window size. Will call {@link #initializeEmulator}
+ * if the emulator is not yet running.
+ *
+ * You should override this method if your application needs to be notified
+ * when the screen size changes (for example, if you need to issue
+ * TIOCSWINSZ to a tty to adjust the window size). If you
+ * do override this method, you must call through to the superclass
+ * implementation.
+ *
+ * @param columns The number of columns in the terminal window.
+ * @param rows The number of rows in the terminal window.
+ */
+ public void updateSize(int columns, int rows) {
+ if (mEmulator == null) {
+ initializeEmulator(columns, rows);
+ } else {
+ mEmulator.updateSize(columns, rows);
+ }
+ }
+
+ /**
+ * Retrieve the terminal's screen and scrollback buffer.
+ *
+ * @return A {@link String} containing the contents of the screen and
+ * scrollback buffer.
+ */
+ public String getTranscriptText() {
+ return mTranscriptScreen.getTranscriptText();
+ }
+
+ /**
+ * Look for new input from the ptty, send it to the terminal emulator.
+ */
+ private void readFromProcess() {
+ int bytesAvailable = mByteQueue.getBytesAvailable();
+ int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
+ int bytesRead = 0;
+ try {
+ bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
+ } catch (InterruptedException e) {
+ return;
+ }
+
+ // Give subclasses a chance to process the read data
+ processInput(mReceiveBuffer, 0, bytesRead);
+ notifyUpdate();
+ }
+
+ /**
+ * Process input and send it to the terminal emulator. This method is
+ * invoked on the main thread whenever new data is read from the
+ * InputStream.
+ *
+ * The default implementation sends the data straight to the terminal
+ * emulator without modifying it in any way. Subclasses can override it to
+ * modify the data before giving it to the terminal.
+ *
+ * @param data A byte array containing the data read.
+ * @param offset The offset into the buffer where the read data begins.
+ * @param count The number of bytes read.
+ */
+ protected void processInput(byte[] data, int offset, int count) {
+ mEmulator.append(data, offset, count);
+ }
+
+ /**
+ * Write something directly to the terminal emulator input, bypassing the
+ * emulation client, the session's {@link InputStream}, and any processing
+ * being done by {@link #processInput processInput}.
+ *
+ * @param data The data to be written to the terminal.
+ * @param offset The starting offset into the buffer of the data.
+ * @param count The length of the data to be written.
+ */
+ protected final void appendToEmulator(byte[] data, int offset, int count) {
+ mEmulator.append(data, offset, count);
+ }
+
+ /**
+ * Set the terminal emulator's color scheme (default colors).
+ *
+ * @param scheme The {@link ColorScheme} to be used (use null for the
+ * default scheme).
+ */
+ public void setColorScheme(ColorScheme scheme) {
+ if (scheme == null) {
+ scheme = BaseTextRenderer.defaultColorScheme;
+ }
+ mColorScheme = scheme;
+ if (mEmulator == null) {
+ return;
+ }
+ mEmulator.setColorScheme(scheme);
+ }
+
+ /**
+ * Set whether the terminal emulator should be in UTF-8 mode by default.
+ *
+ * In UTF-8 mode, the terminal will handle UTF-8 sequences, allowing the
+ * display of text in most of the world's languages, but applications must
+ * encode C1 control characters and graphics drawing characters as the
+ * corresponding UTF-8 sequences.
+ *
+ * @param utf8ByDefault Whether the terminal emulator should be in UTF-8
+ * mode by default.
+ */
+ public void setDefaultUTF8Mode(boolean utf8ByDefault) {
+ mDefaultUTF8Mode = utf8ByDefault;
+ if (mEmulator == null) {
+ return;
+ }
+ mEmulator.setDefaultUTF8Mode(utf8ByDefault);
+ }
+
+ /**
+ * Get whether the terminal emulator is currently in UTF-8 mode.
+ *
+ * @return Whether the emulator is currently in UTF-8 mode.
+ */
+ public boolean getUTF8Mode() {
+ if (mEmulator == null) {
+ return mDefaultUTF8Mode;
+ } else {
+ return mEmulator.getUTF8Mode();
+ }
+ }
+
+ /**
+ * Set an {@link UpdateCallback} to be invoked when the terminal emulator
+ * goes into or out of UTF-8 mode.
+ *
+ * @param utf8ModeNotify The {@link UpdateCallback} to be invoked.
+ */
+ public void setUTF8ModeUpdateCallback(UpdateCallback utf8ModeNotify) {
+ if (mEmulator != null) {
+ mEmulator.setUTF8ModeUpdateCallback(utf8ModeNotify);
+ }
+ }
+
+ /**
+ * Reset the terminal emulator's state.
+ */
+ public void reset() {
+ mEmulator.reset();
+ notifyUpdate();
+ }
+
+ /**
+ * Set a {@link FinishCallback} to be invoked once this terminal session is
+ * finished.
+ *
+ * @param callback The {@link FinishCallback} to be invoked on finish.
+ */
+ public void setFinishCallback(FinishCallback callback) {
+ mFinishCallback = callback;
+ }
+
+ /**
+ * Finish this terminal session. Frees resources used by the terminal
+ * emulator and closes the attached InputStream and
+ * OutputStream.
+ */
+ public void finish() {
+ mIsRunning = false;
+ mEmulator.finish();
+ if (mTranscriptScreen != null) {
+ mTranscriptScreen.finish();
+ }
+
+ // Stop the reader and writer threads, and close the I/O streams
+ if (mWriterHandler != null) {
+ mWriterHandler.sendEmptyMessage(FINISH);
+ }
+ try {
+ mTermIn.close();
+ mTermOut.close();
+ } catch (IOException e) {
+ // We don't care if this fails
+ } catch (NullPointerException e) {
+ }
+
+ if (mFinishCallback != null) {
+ mFinishCallback.onSessionFinish(this);
+ }
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TerminalEmulator.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TerminalEmulator.java
new file mode 100644
index 000000000..9c830b429
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TerminalEmulator.java
@@ -0,0 +1,2011 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.emulatorview;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+import java.util.Locale;
+
+import android.util.Log;
+
+/**
+ * Renders text into a screen. Contains all the terminal-specific knowledge and
+ * state. Emulates a subset of the X Window System xterm terminal, which in turn
+ * is an emulator for a subset of the Digital Equipment Corporation vt100
+ * terminal. Missing functionality: text attributes (bold, underline, reverse
+ * video, color) alternate screen cursor key and keypad escape sequences.
+ */
+class TerminalEmulator {
+ public void setKeyListener(TermKeyListener l) {
+ mKeyListener = l;
+ }
+ private TermKeyListener mKeyListener;
+ /**
+ * The cursor row. Numbered 0..mRows-1.
+ */
+ private int mCursorRow;
+
+ /**
+ * The cursor column. Numbered 0..mColumns-1.
+ */
+ private int mCursorCol;
+
+ /**
+ * The number of character rows in the terminal screen.
+ */
+ private int mRows;
+
+ /**
+ * The number of character columns in the terminal screen.
+ */
+ private int mColumns;
+
+ /**
+ * Stores the characters that appear on the screen of the emulated terminal.
+ */
+ private TranscriptScreen mMainBuffer;
+ private TranscriptScreen mAltBuffer;
+ private TranscriptScreen mScreen;
+
+ /**
+ * The terminal session this emulator is bound to.
+ */
+ private TermSession mSession;
+
+ /**
+ * Keeps track of the current argument of the current escape sequence.
+ * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.)
+ */
+ private int mArgIndex;
+
+ /**
+ * The number of parameter arguments. This name comes from the ANSI standard
+ * for terminal escape codes.
+ */
+ private static final int MAX_ESCAPE_PARAMETERS = 16;
+
+ /**
+ * Holds the arguments of the current escape sequence.
+ */
+ private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
+
+ /**
+ * Holds OSC arguments, which can be strings.
+ */
+ private byte[] mOSCArg = new byte[MAX_OSC_STRING_LENGTH];
+
+ private int mOSCArgLength;
+
+ private int mOSCArgTokenizerIndex;
+
+ /**
+ * Don't know what the actual limit is, this seems OK for now.
+ */
+ private static final int MAX_OSC_STRING_LENGTH = 512;
+
+ // Escape processing states:
+
+ /**
+ * Escape processing state: Not currently in an escape sequence.
+ */
+ private static final int ESC_NONE = 0;
+
+ /**
+ * Escape processing state: Have seen an ESC character
+ */
+ private static final int ESC = 1;
+
+ /**
+ * Escape processing state: Have seen ESC POUND
+ */
+ private static final int ESC_POUND = 2;
+
+ /**
+ * Escape processing state: Have seen ESC and a character-set-select char
+ */
+ private static final int ESC_SELECT_LEFT_PAREN = 3;
+
+ /**
+ * Escape processing state: Have seen ESC and a character-set-select char
+ */
+ private static final int ESC_SELECT_RIGHT_PAREN = 4;
+
+ /**
+ * Escape processing state: ESC [
+ */
+ private static final int ESC_LEFT_SQUARE_BRACKET = 5;
+
+ /**
+ * Escape processing state: ESC [ ?
+ */
+ private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6;
+
+ /**
+ * Escape processing state: ESC %
+ */
+ private static final int ESC_PERCENT = 7;
+
+ /**
+ * Escape processing state: ESC ] (AKA OSC - Operating System Controls)
+ */
+ private static final int ESC_RIGHT_SQUARE_BRACKET = 8;
+
+ /**
+ * Escape processing state: ESC ] (AKA OSC - Operating System Controls)
+ */
+ private static final int ESC_RIGHT_SQUARE_BRACKET_ESC = 9;
+
+ /**
+ * True if the current escape sequence should continue, false if the current
+ * escape sequence should be terminated. Used when parsing a single
+ * character.
+ */
+ private boolean mContinueSequence;
+
+ /**
+ * The current state of the escape sequence state machine.
+ */
+ private int mEscapeState;
+
+ /**
+ * Saved state of the cursor row, Used to implement the save/restore cursor
+ * position escape sequences.
+ */
+ private int mSavedCursorRow;
+
+ /**
+ * Saved state of the cursor column, Used to implement the save/restore
+ * cursor position escape sequences.
+ */
+ private int mSavedCursorCol;
+
+ private int mSavedEffect;
+
+ private int mSavedDecFlags_DECSC_DECRC;
+
+
+ // DecSet booleans
+
+ /**
+ * This mask indicates 132-column mode is set. (As opposed to 80-column
+ * mode.)
+ */
+ private static final int K_132_COLUMN_MODE_MASK = 1 << 3;
+
+ /**
+ * DECSCNM - set means reverse video (light background.)
+ */
+ private static final int K_REVERSE_VIDEO_MASK = 1 << 5;
+
+ /**
+ * This mask indicates that origin mode is set. (Cursor addressing is
+ * relative to the absolute screen size, rather than the currently set top
+ * and bottom margins.)
+ */
+ private static final int K_ORIGIN_MODE_MASK = 1 << 6;
+
+ /**
+ * This mask indicates that wraparound mode is set. (As opposed to
+ * stop-at-right-column mode.)
+ */
+ private static final int K_WRAPAROUND_MODE_MASK = 1 << 7;
+
+ /**
+ * This mask indicates that the cursor should be shown. DECTCEM
+ */
+
+ private static final int K_SHOW_CURSOR_MASK = 1 << 25;
+
+ /** This mask is the subset of DecSet bits that are saved / restored by
+ * the DECSC / DECRC commands
+ */
+ private static final int K_DECSC_DECRC_MASK =
+ K_ORIGIN_MODE_MASK | K_WRAPAROUND_MODE_MASK;
+
+ /**
+ * Holds multiple DECSET flags. The data is stored this way, rather than in
+ * separate booleans, to make it easier to implement the save-and-restore
+ * semantics. The various k*ModeMask masks can be used to extract and modify
+ * the individual flags current states.
+ */
+ private int mDecFlags;
+
+ /**
+ * Saves away a snapshot of the DECSET flags. Used to implement save and
+ * restore escape sequences.
+ */
+ private int mSavedDecFlags;
+
+ /**
+ * The current DECSET mouse tracking mode, zero for no mouse tracking.
+ */
+ private int mMouseTrackingMode;
+
+ // Modes set with Set Mode / Reset Mode
+
+ /**
+ * True if insert mode (as opposed to replace mode) is active. In insert
+ * mode new characters are inserted, pushing existing text to the right.
+ */
+ private boolean mInsertMode;
+
+ /**
+ * An array of tab stops. mTabStop[i] is true if there is a tab stop set for
+ * column i.
+ */
+ private boolean[] mTabStop;
+
+ // The margins allow portions of the screen to be locked.
+
+ /**
+ * The top margin of the screen, for scrolling purposes. Ranges from 0 to
+ * mRows-2.
+ */
+ private int mTopMargin;
+
+ /**
+ * The bottom margin of the screen, for scrolling purposes. Ranges from
+ * mTopMargin + 2 to mRows. (Defines the first row after the scrolling
+ * region.
+ */
+ private int mBottomMargin;
+
+ /**
+ * True if the next character to be emitted will be automatically wrapped to
+ * the next line. Used to disambiguate the case where the cursor is
+ * positioned on column mColumns-1.
+ */
+ private boolean mAboutToAutoWrap;
+
+ /**
+ * The width of the last emitted spacing character. Used to place
+ * combining characters into the correct column.
+ */
+ private int mLastEmittedCharWidth = 0;
+
+ /**
+ * True if we just auto-wrapped and no character has been emitted on this
+ * line yet. Used to ensure combining characters following a character
+ * at the edge of the screen are stored in the proper place.
+ */
+ private boolean mJustWrapped = false;
+
+ /**
+ * Used for debugging, counts how many chars have been processed.
+ */
+ private int mProcessedCharCount;
+
+ /**
+ * Foreground color, 0..255
+ */
+ private int mForeColor;
+ private int mDefaultForeColor;
+
+ /**
+ * Background color, 0..255
+ */
+ private int mBackColor;
+ private int mDefaultBackColor;
+
+ /**
+ * Current TextStyle effect
+ */
+ private int mEffect;
+
+ private boolean mbKeypadApplicationMode;
+
+ /** false == G0, true == G1 */
+ private boolean mAlternateCharSet;
+
+ private final static int CHAR_SET_UK = 0;
+ private final static int CHAR_SET_ASCII = 1;
+ private final static int CHAR_SET_SPECIAL_GRAPHICS = 2;
+ private final static int CHAR_SET_ALT_STANDARD = 3;
+ private final static int CHAR_SET_ALT_SPECIAL_GRAPICS = 4;
+
+ /** What is the current graphics character set. [0] == G0, [1] == G1 */
+ private int[] mCharSet = new int[2];
+
+ /** Derived from mAlternateCharSet and mCharSet.
+ * True if we're supposed to be drawing the special graphics.
+ */
+ private boolean mUseAlternateCharSet;
+
+ /**
+ * Special graphics character set
+ */
+ private static final char[] mSpecialGraphicsCharMap = new char[128];
+ static {
+ for (char i = 0; i < 128; ++i) {
+ mSpecialGraphicsCharMap[i] = i;
+ }
+ mSpecialGraphicsCharMap['_'] = ' '; // Blank
+ mSpecialGraphicsCharMap['b'] = 0x2409; // Tab
+ mSpecialGraphicsCharMap['c'] = 0x240C; // Form feed
+ mSpecialGraphicsCharMap['d'] = 0x240D; // Carriage return
+ mSpecialGraphicsCharMap['e'] = 0x240A; // Line feed
+ mSpecialGraphicsCharMap['h'] = 0x2424; // New line
+ mSpecialGraphicsCharMap['i'] = 0x240B; // Vertical tab/"lantern"
+ mSpecialGraphicsCharMap['}'] = 0x00A3; // Pound sterling symbol
+ mSpecialGraphicsCharMap['f'] = 0x00B0; // Degree symbol
+ mSpecialGraphicsCharMap['`'] = 0x2B25; // Diamond
+ mSpecialGraphicsCharMap['~'] = 0x2022; // Bullet point
+ mSpecialGraphicsCharMap['y'] = 0x2264; // Less-than-or-equals sign (<=)
+ mSpecialGraphicsCharMap['|'] = 0x2260; // Not equals sign (!=)
+ mSpecialGraphicsCharMap['z'] = 0x2265; // Greater-than-or-equals sign (>=)
+ mSpecialGraphicsCharMap['g'] = 0x00B1; // Plus-or-minus sign (+/-)
+ mSpecialGraphicsCharMap['{'] = 0x03C0; // Lowercase Greek letter pi
+ mSpecialGraphicsCharMap['.'] = 0x25BC; // Down arrow
+ mSpecialGraphicsCharMap[','] = 0x25C0; // Left arrow
+ mSpecialGraphicsCharMap['+'] = 0x25B6; // Right arrow
+ mSpecialGraphicsCharMap['-'] = 0x25B2; // Up arrow
+ mSpecialGraphicsCharMap['h'] = '#'; // Board of squares
+ mSpecialGraphicsCharMap['a'] = 0x2592; // Checkerboard
+ mSpecialGraphicsCharMap['0'] = 0x2588; // Solid block
+ mSpecialGraphicsCharMap['q'] = 0x2500; // Horizontal line (box drawing)
+ mSpecialGraphicsCharMap['x'] = 0x2502; // Vertical line (box drawing)
+ mSpecialGraphicsCharMap['m'] = 0x2514; // Lower left hand corner (box drawing)
+ mSpecialGraphicsCharMap['j'] = 0x2518; // Lower right hand corner (box drawing)
+ mSpecialGraphicsCharMap['l'] = 0x250C; // Upper left hand corner (box drawing)
+ mSpecialGraphicsCharMap['k'] = 0x2510; // Upper right hand corner (box drawing)
+ mSpecialGraphicsCharMap['w'] = 0x252C; // T pointing downwards (box drawing)
+ mSpecialGraphicsCharMap['u'] = 0x2524; // T pointing leftwards (box drawing)
+ mSpecialGraphicsCharMap['t'] = 0x251C; // T pointing rightwards (box drawing)
+ mSpecialGraphicsCharMap['v'] = 0x2534; // T pointing upwards (box drawing)
+ mSpecialGraphicsCharMap['n'] = 0x253C; // Large plus/lines crossing (box drawing)
+ mSpecialGraphicsCharMap['o'] = 0x23BA; // Horizontal scanline 1
+ mSpecialGraphicsCharMap['p'] = 0x23BB; // Horizontal scanline 3
+ mSpecialGraphicsCharMap['r'] = 0x23BC; // Horizontal scanline 7
+ mSpecialGraphicsCharMap['s'] = 0x23BD; // Horizontal scanline 9
+ }
+
+ /**
+ * Used for moving selection up along with the scrolling text
+ */
+ private int mScrollCounter = 0;
+
+ /**
+ * UTF-8 support
+ */
+ private static final int UNICODE_REPLACEMENT_CHAR = 0xfffd;
+ private boolean mDefaultUTF8Mode = false;
+ private boolean mUTF8Mode = false;
+ private boolean mUTF8EscapeUsed = false;
+ private int mUTF8ToFollow = 0;
+ private ByteBuffer mUTF8ByteBuffer;
+ private CharBuffer mInputCharBuffer;
+ private CharsetDecoder mUTF8Decoder;
+ private UpdateCallback mUTF8ModeNotify;
+
+ /** This is not accurate, but it makes the terminal more useful on
+ * small screens.
+ */
+ private final static boolean DEFAULT_TO_AUTOWRAP_ENABLED = true;
+
+ /**
+ * Construct a terminal emulator that uses the supplied screen
+ *
+ * @param session the terminal session the emulator is attached to
+ * @param screen the screen to render characters into.
+ * @param columns the number of columns to emulate
+ * @param rows the number of rows to emulate
+ * @param scheme the default color scheme of this emulator
+ */
+ public TerminalEmulator(TermSession session, TranscriptScreen screen, int columns, int rows, ColorScheme scheme) {
+ mSession = session;
+ mMainBuffer = screen;
+ mScreen = mMainBuffer;
+ mAltBuffer = new TranscriptScreen(columns, rows, rows, scheme);
+ mRows = rows;
+ mColumns = columns;
+ mTabStop = new boolean[mColumns];
+
+ setColorScheme(scheme);
+
+ mUTF8ByteBuffer = ByteBuffer.allocate(4);
+ mInputCharBuffer = CharBuffer.allocate(2);
+ mUTF8Decoder = Charset.forName("UTF-8").newDecoder();
+ mUTF8Decoder.onMalformedInput(CodingErrorAction.REPLACE);
+ mUTF8Decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+
+ reset();
+ }
+
+ public TranscriptScreen getScreen() {
+ return mScreen;
+ }
+
+ public void updateSize(int columns, int rows) {
+ if (mRows == rows && mColumns == columns) {
+ return;
+ }
+ if (columns <= 0) {
+ throw new IllegalArgumentException("rows:" + columns);
+ }
+
+ if (rows <= 0) {
+ throw new IllegalArgumentException("rows:" + rows);
+ }
+
+ TranscriptScreen screen = mScreen;
+ TranscriptScreen altScreen;
+ if (screen != mMainBuffer) {
+ altScreen = mMainBuffer;
+ } else {
+ altScreen = mAltBuffer;
+ }
+
+ // Try to resize the screen without getting the transcript
+ int[] cursor = { mCursorCol, mCursorRow };
+ boolean fastResize = screen.fastResize(columns, rows, cursor);
+
+ GrowableIntArray cursorColor = null;
+ String charAtCursor = null;
+ GrowableIntArray colors = null;
+ String transcriptText = null;
+ if (!fastResize) {
+ /* Save the character at the cursor (if one exists) and store an
+ * ASCII ESC character at the cursor's location
+ * This is an epic hack that lets us restore the cursor later...
+ */
+ cursorColor = new GrowableIntArray(1);
+ charAtCursor = screen.getSelectedText(cursorColor, mCursorCol, mCursorRow, mCursorCol, mCursorRow);
+ screen.set(mCursorCol, mCursorRow, 27, 0);
+
+ colors = new GrowableIntArray(1024);
+ transcriptText = screen.getTranscriptText(colors);
+ screen.resize(columns, rows, getStyle());
+ }
+
+ boolean altFastResize = true;
+ GrowableIntArray altColors = null;
+ String altTranscriptText = null;
+ if (altScreen != null) {
+ altFastResize = altScreen.fastResize(columns, rows, null);
+
+ if (!altFastResize) {
+ altColors = new GrowableIntArray(1024);
+ altTranscriptText = altScreen.getTranscriptText(altColors);
+ altScreen.resize(columns, rows, getStyle());
+ }
+ }
+
+ if (mRows != rows) {
+ mRows = rows;
+ mTopMargin = 0;
+ mBottomMargin = mRows;
+ }
+ if (mColumns != columns) {
+ int oldColumns = mColumns;
+ mColumns = columns;
+ boolean[] oldTabStop = mTabStop;
+ mTabStop = new boolean[mColumns];
+ int toTransfer = Math.min(oldColumns, columns);
+ System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
+ }
+
+ if (!altFastResize) {
+ boolean wasAboutToAutoWrap = mAboutToAutoWrap;
+
+ // Restore the contents of the inactive screen's buffer
+ mScreen = altScreen;
+ mCursorRow = 0;
+ mCursorCol = 0;
+ mAboutToAutoWrap = false;
+
+ int end = altTranscriptText.length()-1;
+ /* Unlike for the main transcript below, don't trim off trailing
+ * newlines -- the alternate transcript lacks a cursor marking, so
+ * we might introduce an unwanted vertical shift in the screen
+ * contents this way */
+ char c, cLow;
+ int colorOffset = 0;
+ for (int i = 0; i <= end; i++) {
+ c = altTranscriptText.charAt(i);
+ int style = altColors.at(i-colorOffset);
+ if (Character.isHighSurrogate(c)) {
+ cLow = altTranscriptText.charAt(++i);
+ emit(Character.toCodePoint(c, cLow), style);
+ ++colorOffset;
+ } else if (c == '\n') {
+ setCursorCol(0);
+ doLinefeed();
+ } else {
+ emit(c, style);
+ }
+ }
+
+ mScreen = screen;
+ mAboutToAutoWrap = wasAboutToAutoWrap;
+ }
+
+ if (fastResize) {
+ // Only need to make sure the cursor is in the right spot
+ if (cursor[0] >= 0 && cursor[1] >= 0) {
+ mCursorCol = cursor[0];
+ mCursorRow = cursor[1];
+ } else {
+ // Cursor scrolled off screen, reset the cursor to top left
+ mCursorCol = 0;
+ mCursorRow = 0;
+ }
+
+ return;
+ }
+
+ mCursorRow = 0;
+ mCursorCol = 0;
+ mAboutToAutoWrap = false;
+
+ int newCursorRow = -1;
+ int newCursorCol = -1;
+ int newCursorTranscriptPos = -1;
+ int end = transcriptText.length()-1;
+ while ((end >= 0) && transcriptText.charAt(end) == '\n') {
+ end--;
+ }
+ char c, cLow;
+ int colorOffset = 0;
+ for(int i = 0; i <= end; i++) {
+ c = transcriptText.charAt(i);
+ int style = colors.at(i-colorOffset);
+ if (Character.isHighSurrogate(c)) {
+ cLow = transcriptText.charAt(++i);
+ emit(Character.toCodePoint(c, cLow), style);
+ ++colorOffset;
+ } else if (c == '\n') {
+ setCursorCol(0);
+ doLinefeed();
+ } else if (c == 27) {
+ /* We marked the cursor location with ESC earlier, so this
+ is the place to restore the cursor to */
+ newCursorRow = mCursorRow;
+ newCursorCol = mCursorCol;
+ newCursorTranscriptPos = screen.getActiveRows();
+ if (charAtCursor != null && charAtCursor.length() > 0) {
+ // Emit the real character that was in this spot
+ int encodedCursorColor = cursorColor.at(0);
+ emit(charAtCursor.toCharArray(), 0, charAtCursor.length(), encodedCursorColor);
+ }
+ } else {
+ emit(c, style);
+ }
+ }
+
+ // If we marked a cursor location, move the cursor there now
+ if (newCursorRow != -1 && newCursorCol != -1) {
+ mCursorRow = newCursorRow;
+ mCursorCol = newCursorCol;
+
+ /* Adjust for any scrolling between the time we marked the cursor
+ location and now */
+ int scrollCount = screen.getActiveRows() - newCursorTranscriptPos;
+ if (scrollCount > 0 && scrollCount <= newCursorRow) {
+ mCursorRow -= scrollCount;
+ } else if (scrollCount > newCursorRow) {
+ // Cursor scrolled off screen -- reset to top left corner
+ mCursorRow = 0;
+ mCursorCol = 0;
+ }
+ }
+ }
+
+ /**
+ * Get the cursor's current row.
+ *
+ * @return the cursor's current row.
+ */
+ public final int getCursorRow() {
+ return mCursorRow;
+ }
+
+ /**
+ * Get the cursor's current column.
+ *
+ * @return the cursor's current column.
+ */
+ public final int getCursorCol() {
+ return mCursorCol;
+ }
+
+ public final boolean getReverseVideo() {
+ return (mDecFlags & K_REVERSE_VIDEO_MASK) != 0;
+ }
+
+ public final boolean getShowCursor() {
+ return (mDecFlags & K_SHOW_CURSOR_MASK) != 0;
+ }
+
+ public final boolean getKeypadApplicationMode() {
+ return mbKeypadApplicationMode;
+ }
+
+ /**
+ * Get the current DECSET mouse tracking mode, zero for no mouse tracking.
+ *
+ * @return the current DECSET mouse tracking mode.
+ */
+ public final int getMouseTrackingMode() {
+ return mMouseTrackingMode;
+ }
+
+ private void setDefaultTabStops() {
+ for (int i = 0; i < mColumns; i++) {
+ mTabStop[i] = (i & 7) == 0 && i != 0;
+ }
+ }
+
+ /**
+ * Accept bytes (typically from the pseudo-teletype) and process them.
+ *
+ * @param buffer a byte array containing the bytes to be processed
+ * @param base the first index of the array to process
+ * @param length the number of bytes in the array to process
+ */
+ public void append(byte[] buffer, int base, int length) {
+ if (EmulatorDebug.LOG_CHARACTERS_FLAG) {
+ Log.d(EmulatorDebug.LOG_TAG, "In: '" + EmulatorDebug.bytesToString(buffer, base, length) + "'");
+ }
+ for (int i = 0; i < length; i++) {
+ byte b = buffer[base + i];
+ try {
+ process(b);
+ mProcessedCharCount++;
+ } catch (Exception e) {
+ Log.e(EmulatorDebug.LOG_TAG, "Exception while processing character "
+ + Integer.toString(mProcessedCharCount) + " code "
+ + Integer.toString(b), e);
+ }
+ }
+ }
+
+ private void process(byte b) {
+ process(b, true);
+ }
+
+ private void process(byte b, boolean doUTF8) {
+ // Let the UTF-8 decoder try to handle it if we're in UTF-8 mode
+ if (doUTF8 && mUTF8Mode && handleUTF8Sequence(b)) {
+ return;
+ }
+
+ // Handle C1 control characters
+ if ((b & 0x80) == 0x80 && (b & 0x7f) <= 0x1f) {
+ /* ESC ((code & 0x7f) + 0x40) is the two-byte escape sequence
+ corresponding to a particular C1 code */
+ process((byte) 27, false);
+ process((byte) ((b & 0x7f) + 0x40), false);
+ return;
+ }
+
+ switch (b) {
+ case 0: // NUL
+ // Do nothing
+ break;
+
+ case 7: // BEL
+ /* If in an OSC sequence, BEL may terminate a string; otherwise do
+ * nothing */
+ if (mEscapeState == ESC_RIGHT_SQUARE_BRACKET) {
+ doEscRightSquareBracket(b);
+ }
+ break;
+
+ case 8: // BS
+ setCursorCol(Math.max(0, mCursorCol - 1));
+ break;
+
+ case 9: // HT
+ // Move to next tab stop, but not past edge of screen
+ setCursorCol(nextTabStop(mCursorCol));
+ break;
+
+ case 13:
+ setCursorCol(0);
+ break;
+
+ case 10: // CR
+ case 11: // VT
+ case 12: // LF
+ doLinefeed();
+ break;
+
+ case 14: // SO:
+ setAltCharSet(true);
+ break;
+
+ case 15: // SI:
+ setAltCharSet(false);
+ break;
+
+
+ case 24: // CAN
+ case 26: // SUB
+ if (mEscapeState != ESC_NONE) {
+ mEscapeState = ESC_NONE;
+ emit((byte) 127);
+ }
+ break;
+
+ case 27: // ESC
+ // Starts an escape sequence unless we're parsing a string
+ if (mEscapeState != ESC_RIGHT_SQUARE_BRACKET) {
+ startEscapeSequence(ESC);
+ } else {
+ doEscRightSquareBracket(b);
+ }
+ break;
+
+ default:
+ mContinueSequence = false;
+ switch (mEscapeState) {
+ case ESC_NONE:
+ if (b >= 32) {
+ emit(b);
+ }
+ break;
+
+ case ESC:
+ doEsc(b);
+ break;
+
+ case ESC_POUND:
+ doEscPound(b);
+ break;
+
+ case ESC_SELECT_LEFT_PAREN:
+ doEscSelectLeftParen(b);
+ break;
+
+ case ESC_SELECT_RIGHT_PAREN:
+ doEscSelectRightParen(b);
+ break;
+
+ case ESC_LEFT_SQUARE_BRACKET:
+ doEscLeftSquareBracket(b); // CSI
+ break;
+
+ case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
+ doEscLSBQuest(b); // CSI ?
+ break;
+
+ case ESC_PERCENT:
+ doEscPercent(b);
+ break;
+
+ case ESC_RIGHT_SQUARE_BRACKET:
+ doEscRightSquareBracket(b);
+ break;
+
+ case ESC_RIGHT_SQUARE_BRACKET_ESC:
+ doEscRightSquareBracketEsc(b);
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ if (!mContinueSequence) {
+ mEscapeState = ESC_NONE;
+ }
+ break;
+ }
+ }
+
+ private boolean handleUTF8Sequence(byte b) {
+ if (mUTF8ToFollow == 0 && (b & 0x80) == 0) {
+ // ASCII character -- we don't need to handle this
+ return false;
+ }
+
+ if (mUTF8ToFollow > 0) {
+ if ((b & 0xc0) != 0x80) {
+ /* Not a UTF-8 continuation byte (doesn't begin with 0b10)
+ Replace the entire sequence with the replacement char */
+ mUTF8ToFollow = 0;
+ mUTF8ByteBuffer.clear();
+ emit(UNICODE_REPLACEMENT_CHAR);
+
+ /* The Unicode standard (section 3.9, definition D93) requires
+ * that we now attempt to process this byte as though it were
+ * the beginning of another possibly-valid sequence */
+ return handleUTF8Sequence(b);
+ }
+
+ mUTF8ByteBuffer.put(b);
+ if (--mUTF8ToFollow == 0) {
+ // Sequence complete -- decode and emit it
+ ByteBuffer byteBuf = mUTF8ByteBuffer;
+ CharBuffer charBuf = mInputCharBuffer;
+ CharsetDecoder decoder = mUTF8Decoder;
+
+ byteBuf.rewind();
+ decoder.reset();
+ decoder.decode(byteBuf, charBuf, true);
+ decoder.flush(charBuf);
+
+ char[] chars = charBuf.array();
+ if (chars[0] >= 0x80 && chars[0] <= 0x9f) {
+ /* Sequence decoded to a C1 control character which needs
+ to be sent through process() again */
+ process((byte) chars[0], false);
+ } else {
+ emit(chars);
+ }
+
+ byteBuf.clear();
+ charBuf.clear();
+ }
+ } else {
+ if ((b & 0xe0) == 0xc0) { // 0b110 -- two-byte sequence
+ mUTF8ToFollow = 1;
+ } else if ((b & 0xf0) == 0xe0) { // 0b1110 -- three-byte sequence
+ mUTF8ToFollow = 2;
+ } else if ((b & 0xf8) == 0xf0) { // 0b11110 -- four-byte sequence
+ mUTF8ToFollow = 3;
+ } else {
+ // Not a valid UTF-8 sequence start -- replace this char
+ emit(UNICODE_REPLACEMENT_CHAR);
+ return true;
+ }
+
+ mUTF8ByteBuffer.put(b);
+ }
+
+ return true;
+ }
+
+ private void setAltCharSet(boolean alternateCharSet) {
+ mAlternateCharSet = alternateCharSet;
+ computeEffectiveCharSet();
+ }
+
+ private void computeEffectiveCharSet() {
+ int charSet = mCharSet[mAlternateCharSet ? 1 : 0];
+ mUseAlternateCharSet = charSet == CHAR_SET_SPECIAL_GRAPHICS;
+ }
+
+ private int nextTabStop(int cursorCol) {
+ for (int i = cursorCol + 1; i < mColumns; i++) {
+ if (mTabStop[i]) {
+ return i;
+ }
+ }
+ return mColumns - 1;
+ }
+
+ private int prevTabStop(int cursorCol) {
+ for (int i = cursorCol - 1; i >= 0; i--) {
+ if (mTabStop[i]) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ private void doEscPercent(byte b) {
+ switch (b) {
+ case '@': // Esc % @ -- return to ISO 2022 mode
+ setUTF8Mode(false);
+ mUTF8EscapeUsed = true;
+ break;
+ case 'G': // Esc % G -- UTF-8 mode
+ setUTF8Mode(true);
+ mUTF8EscapeUsed = true;
+ break;
+ default: // unimplemented character set
+ break;
+ }
+ }
+
+ private void doEscLSBQuest(byte b) {
+ int arg = getArg0(0);
+ int mask = getDecFlagsMask(arg);
+ int oldFlags = mDecFlags;
+ switch (b) {
+ case 'h': // Esc [ ? Pn h - DECSET
+ mDecFlags |= mask;
+ switch (arg) {
+ case 1:
+ mKeyListener.setCursorKeysApplicationMode(true);
+ break;
+ case 47:
+ case 1047:
+ case 1049:
+ if (mAltBuffer != null) {
+ mScreen = mAltBuffer;
+ }
+ break;
+ }
+ if (arg >= 1000 && arg <= 1003) {
+ mMouseTrackingMode = arg;
+ }
+ break;
+
+ case 'l': // Esc [ ? Pn l - DECRST
+ mDecFlags &= ~mask;
+ switch (arg) {
+ case 1:
+ mKeyListener.setCursorKeysApplicationMode(false);
+ break;
+ case 47:
+ case 1047:
+ case 1049:
+ mScreen = mMainBuffer;
+ break;
+ }
+ if (arg >= 1000 && arg <= 1003) {
+ mMouseTrackingMode = 0;
+ }
+ break;
+
+ case 'r': // Esc [ ? Pn r - restore
+ mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask);
+ break;
+
+ case 's': // Esc [ ? Pn s - save
+ mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask);
+ break;
+
+ default:
+ parseArg(b);
+ break;
+ }
+
+ int newlySetFlags = (~oldFlags) & mDecFlags;
+ int changedFlags = oldFlags ^ mDecFlags;
+
+ // 132 column mode
+ if ((changedFlags & K_132_COLUMN_MODE_MASK) != 0) {
+ // We don't actually set/reset 132 cols, but we do want the
+ // side effect of clearing the screen and homing the cursor.
+ blockClear(0, 0, mColumns, mRows);
+ setCursorRowCol(0, 0);
+ }
+
+ // origin mode
+ if ((newlySetFlags & K_ORIGIN_MODE_MASK) != 0) {
+ // Home the cursor.
+ setCursorPosition(0, 0);
+ }
+ }
+
+ private int getDecFlagsMask(int argument) {
+ if (argument >= 1 && argument <= 32) {
+ return (1 << argument);
+ }
+
+ return 0;
+ }
+
+ private void startEscapeSequence(int escapeState) {
+ mEscapeState = escapeState;
+ mArgIndex = 0;
+ for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) {
+ mArgs[j] = -1;
+ }
+ }
+
+ private void doLinefeed() {
+ int newCursorRow = mCursorRow + 1;
+ if (newCursorRow >= mBottomMargin) {
+ scroll();
+ newCursorRow = mBottomMargin - 1;
+ }
+ setCursorRow(newCursorRow);
+ }
+
+ private void continueSequence() {
+ mContinueSequence = true;
+ }
+
+ private void continueSequence(int state) {
+ mEscapeState = state;
+ mContinueSequence = true;
+ }
+
+ private void doEscSelectLeftParen(byte b) {
+ doSelectCharSet(0, b);
+ }
+
+ private void doEscSelectRightParen(byte b) {
+ doSelectCharSet(1, b);
+ }
+
+ private void doSelectCharSet(int charSetIndex, byte b) {
+ int charSet;
+ switch (b) {
+ case 'A': // United Kingdom character set
+ charSet = CHAR_SET_UK;
+ break;
+ case 'B': // ASCII set
+ charSet = CHAR_SET_ASCII;
+ break;
+ case '0': // Special Graphics
+ charSet = CHAR_SET_SPECIAL_GRAPHICS;
+ break;
+ case '1': // Alternate character set
+ charSet = CHAR_SET_ALT_STANDARD;
+ break;
+ case '2':
+ charSet = CHAR_SET_ALT_SPECIAL_GRAPICS;
+ break;
+ default:
+ unknownSequence(b);
+ return;
+ }
+ mCharSet[charSetIndex] = charSet;
+ computeEffectiveCharSet();
+ }
+
+ private void doEscPound(byte b) {
+ switch (b) {
+ case '8': // Esc # 8 - DECALN alignment test
+ mScreen.blockSet(0, 0, mColumns, mRows, 'E',
+ getStyle());
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ }
+
+ private void doEsc(byte b) {
+ switch (b) {
+ case '#':
+ continueSequence(ESC_POUND);
+ break;
+
+ case '(':
+ continueSequence(ESC_SELECT_LEFT_PAREN);
+ break;
+
+ case ')':
+ continueSequence(ESC_SELECT_RIGHT_PAREN);
+ break;
+
+ case '7': // DECSC save cursor
+ mSavedCursorRow = mCursorRow;
+ mSavedCursorCol = mCursorCol;
+ mSavedEffect = mEffect;
+ mSavedDecFlags_DECSC_DECRC = mDecFlags & K_DECSC_DECRC_MASK;
+ break;
+
+ case '8': // DECRC restore cursor
+ setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
+ mEffect = mSavedEffect;
+ mDecFlags = (mDecFlags & ~ K_DECSC_DECRC_MASK)
+ | mSavedDecFlags_DECSC_DECRC;
+ break;
+
+ case 'D': // INDEX
+ doLinefeed();
+ break;
+
+ case 'E': // NEL
+ setCursorCol(0);
+ doLinefeed();
+ break;
+
+ case 'F': // Cursor to lower-left corner of screen
+ setCursorRowCol(0, mBottomMargin - 1);
+ break;
+
+ case 'H': // Tab set
+ mTabStop[mCursorCol] = true;
+ break;
+
+ case 'M': // Reverse index
+ if (mCursorRow <= mTopMargin) {
+ mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin
+ - (mTopMargin + 1), 0, mTopMargin + 1);
+ blockClear(0, mTopMargin, mColumns);
+ } else {
+ mCursorRow--;
+ }
+
+ break;
+
+ case 'N': // SS2
+ unimplementedSequence(b);
+ break;
+
+ case '0': // SS3
+ unimplementedSequence(b);
+ break;
+
+ case 'P': // Device control string
+ unimplementedSequence(b);
+ break;
+
+ case 'Z': // return terminal ID
+ sendDeviceAttributes();
+ break;
+
+ case '[':
+ continueSequence(ESC_LEFT_SQUARE_BRACKET);
+ break;
+
+ case '=': // DECKPAM
+ mbKeypadApplicationMode = true;
+ break;
+
+ case ']': // OSC
+ startCollectingOSCArgs();
+ continueSequence(ESC_RIGHT_SQUARE_BRACKET);
+ break;
+
+ case '>' : // DECKPNM
+ mbKeypadApplicationMode = false;
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ }
+
+ private void doEscLeftSquareBracket(byte b) {
+ // CSI
+ switch (b) {
+ case '@': // ESC [ Pn @ - ICH Insert Characters
+ {
+ int charsAfterCursor = mColumns - mCursorCol;
+ int charsToInsert = Math.min(getArg0(1), charsAfterCursor);
+ int charsToMove = charsAfterCursor - charsToInsert;
+ mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1,
+ mCursorCol + charsToInsert, mCursorRow);
+ blockClear(mCursorCol, mCursorRow, charsToInsert);
+ }
+ break;
+
+ case 'A': // ESC [ Pn A - Cursor Up
+ setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
+ break;
+
+ case 'B': // ESC [ Pn B - Cursor Down
+ setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
+ break;
+
+ case 'C': // ESC [ Pn C - Cursor Right
+ setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1)));
+ break;
+
+ case 'D': // ESC [ Pn D - Cursor Left
+ setCursorCol(Math.max(0, mCursorCol - getArg0(1)));
+ break;
+
+ case 'G': // ESC [ Pn G - Cursor Horizontal Absolute
+ setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
+ break;
+
+ case 'H': // ESC [ Pn ; H - Cursor Position
+ setHorizontalVerticalPosition();
+ break;
+
+ case 'J': // ESC [ Pn J - ED - Erase in Display
+ // ED ignores the scrolling margins.
+ switch (getArg0(0)) {
+ case 0: // Clear below
+ blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
+ blockClear(0, mCursorRow + 1, mColumns,
+ mRows - (mCursorRow + 1));
+ break;
+
+ case 1: // Erase from the start of the screen to the cursor.
+ blockClear(0, 0, mColumns, mCursorRow);
+ blockClear(0, mCursorRow, mCursorCol + 1);
+ break;
+
+ case 2: // Clear all
+ blockClear(0, 0, mColumns, mRows);
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ break;
+
+ case 'K': // ESC [ Pn K - Erase in Line
+ switch (getArg0(0)) {
+ case 0: // Clear to right
+ blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
+ break;
+
+ case 1: // Erase start of line to cursor (including cursor)
+ blockClear(0, mCursorRow, mCursorCol + 1);
+ break;
+
+ case 2: // Clear whole line
+ blockClear(0, mCursorRow, mColumns);
+ break;
+
+ default:
+ unknownSequence(b);
+ break;
+ }
+ break;
+
+ case 'L': // Insert Lines
+ {
+ int linesAfterCursor = mBottomMargin - mCursorRow;
+ int linesToInsert = Math.min(getArg0(1), linesAfterCursor);
+ int linesToMove = linesAfterCursor - linesToInsert;
+ mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0,
+ mCursorRow + linesToInsert);
+ blockClear(0, mCursorRow, mColumns, linesToInsert);
+ }
+ break;
+
+ case 'M': // Delete Lines
+ {
+ int linesAfterCursor = mBottomMargin - mCursorRow;
+ int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
+ int linesToMove = linesAfterCursor - linesToDelete;
+ mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns,
+ linesToMove, 0, mCursorRow);
+ blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete);
+ }
+ break;
+
+ case 'P': // Delete Characters
+ {
+ int charsAfterCursor = mColumns - mCursorCol;
+ int charsToDelete = Math.min(getArg0(1), charsAfterCursor);
+ int charsToMove = charsAfterCursor - charsToDelete;
+ mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow,
+ charsToMove, 1, mCursorCol, mCursorRow);
+ blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete);
+ }
+ break;
+
+ case 'T': // Mouse tracking
+ unimplementedSequence(b);
+ break;
+
+ case 'X': // Erase characters
+ blockClear(mCursorCol, mCursorRow, getArg0(0));
+ break;
+
+ case 'Z': // Back tab
+ setCursorCol(prevTabStop(mCursorCol));
+ break;
+
+ case '?': // Esc [ ? -- start of a private mode set
+ continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK);
+ break;
+
+ case 'c': // Send device attributes
+ sendDeviceAttributes();
+ break;
+
+ case 'd': // ESC [ Pn d - Vert Position Absolute
+ setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
+ break;
+
+ case 'f': // Horizontal and Vertical Position
+ setHorizontalVerticalPosition();
+ break;
+
+ case 'g': // Clear tab stop
+ switch (getArg0(0)) {
+ case 0:
+ mTabStop[mCursorCol] = false;
+ break;
+
+ case 3:
+ for (int i = 0; i < mColumns; i++) {
+ mTabStop[i] = false;
+ }
+ break;
+
+ default:
+ // Specified to have no effect.
+ break;
+ }
+ break;
+
+ case 'h': // Set Mode
+ doSetMode(true);
+ break;
+
+ case 'l': // Reset Mode
+ doSetMode(false);
+ break;
+
+ case 'm': // Esc [ Pn m - character attributes.
+ // (can have up to 16 numerical arguments)
+ selectGraphicRendition();
+ break;
+
+ case 'n': // Esc [ Pn n - ECMA-48 Status Report Commands
+ //sendDeviceAttributes()
+ switch (getArg0(0)) {
+ case 5: // Device status report (DSR):
+ // Answer is ESC [ 0 n (Terminal OK).
+ byte[] dsr = { (byte) 27, (byte) '[', (byte) '0', (byte) 'n' };
+ mSession.write(dsr, 0, dsr.length);
+ break;
+
+ case 6: // Cursor position report (CPR):
+ // Answer is ESC [ y ; x R, where x,y is
+ // the cursor location.
+ byte[] cpr = String.format(Locale.US, "\033[%d;%dR",
+ mCursorRow + 1, mCursorCol + 1).getBytes();
+ mSession.write(cpr, 0, cpr.length);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case 'r': // Esc [ Pn ; Pn r - set top and bottom margins
+ {
+ // The top margin defaults to 1, the bottom margin
+ // (unusually for arguments) defaults to mRows.
+ //
+ // The escape sequence numbers top 1..23, but we
+ // number top 0..22.
+ // The escape sequence numbers bottom 2..24, and
+ // so do we (because we use a zero based numbering
+ // scheme, but we store the first line below the
+ // bottom-most scrolling line.
+ // As a result, we adjust the top line by -1, but
+ // we leave the bottom line alone.
+ //
+ // Also require that top + 2 <= bottom
+
+ int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
+ int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows));
+ mTopMargin = top;
+ mBottomMargin = bottom;
+
+ // The cursor is placed in the home position
+ setCursorRowCol(mTopMargin, 0);
+ }
+ break;
+
+ default:
+ parseArg(b);
+ break;
+ }
+ }
+
+ private void selectGraphicRendition() {
+ // SGR
+ for (int i = 0; i <= mArgIndex; i++) {
+ int code = mArgs[i];
+ if ( code < 0) {
+ if (mArgIndex > 0) {
+ continue;
+ } else {
+ code = 0;
+ }
+ }
+
+ // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+
+ if (code == 0) { // reset
+ mForeColor = mDefaultForeColor;
+ mBackColor = mDefaultBackColor;
+ mEffect = TextStyle.fxNormal;
+ } else if (code == 1) { // bold
+ mEffect |= TextStyle.fxBold;
+ } else if (code == 3) { // italics, but rarely used as such; "standout" (inverse colors) with TERM=screen
+ mEffect |= TextStyle.fxItalic;
+ } else if (code == 4) { // underscore
+ mEffect |= TextStyle.fxUnderline;
+ } else if (code == 5) { // blink
+ mEffect |= TextStyle.fxBlink;
+ } else if (code == 7) { // inverse
+ mEffect |= TextStyle.fxInverse;
+ } else if (code == 8) { // invisible
+ mEffect |= TextStyle.fxInvisible;
+ } else if (code == 10) { // exit alt charset (TERM=linux)
+ setAltCharSet(false);
+ } else if (code == 11) { // enter alt charset (TERM=linux)
+ setAltCharSet(true);
+ } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint
+ //mEffect &= ~(TextStyle.fxBold | TextStyle.fxFaint);
+ mEffect &= ~TextStyle.fxBold;
+ } else if (code == 23) { // not italic, but rarely used as such; clears standout with TERM=screen
+ mEffect &= ~TextStyle.fxItalic;
+ } else if (code == 24) { // underline: none
+ mEffect &= ~TextStyle.fxUnderline;
+ } else if (code == 25) { // blink: none
+ mEffect &= ~TextStyle.fxBlink;
+ } else if (code == 27) { // image: positive
+ mEffect &= ~TextStyle.fxInverse;
+ } else if (code == 28) { // invisible
+ mEffect &= ~TextStyle.fxInvisible;
+ } else if (code >= 30 && code <= 37) { // foreground color
+ mForeColor = code - 30;
+ } else if (code == 38 && i+2 <= mArgIndex && mArgs[i+1] == 5) { // foreground 256 color
+ int color = mArgs[i+2];
+ if (checkColor(color)) {
+ mForeColor = color;
+ }
+ i += 2;
+ } else if (code == 39) { // set default text color
+ mForeColor = mDefaultForeColor;
+ } else if (code >= 40 && code <= 47) { // background color
+ mBackColor = code - 40;
+ } else if (code == 48 && i+2 <= mArgIndex && mArgs[i+1] == 5) { // background 256 color
+ mBackColor = mArgs[i+2];
+ int color = mArgs[i+2];
+ if (checkColor(color)) {
+ mBackColor = color;
+ }
+ i += 2;
+ } else if (code == 49) { // set default background color
+ mBackColor = mDefaultBackColor;
+ } else if (code >= 90 && code <= 97) { // bright foreground color
+ mForeColor = code - 90 + 8;
+ } else if (code >= 100 && code <= 107) { // bright background color
+ mBackColor = code - 100 + 8;
+ } else {
+ if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ Log.w(EmulatorDebug.LOG_TAG, String.format("SGR unknown code %d", code));
+ }
+ }
+ }
+ }
+
+ private boolean checkColor(int color) {
+ boolean result = isValidColor(color);
+ if (!result) {
+ if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ Log.w(EmulatorDebug.LOG_TAG,
+ String.format("Invalid color %d", color));
+ }
+ }
+ return result;
+ }
+
+ private boolean isValidColor(int color) {
+ return color >= 0 && color < TextStyle.ciColorLength;
+ }
+
+ private void doEscRightSquareBracket(byte b) {
+ switch (b) {
+ case 0x7:
+ doOSC();
+ break;
+ case 0x1b: // Esc, probably start of Esc \ sequence
+ continueSequence(ESC_RIGHT_SQUARE_BRACKET_ESC);
+ break;
+ default:
+ collectOSCArgs(b);
+ break;
+ }
+ }
+
+ private void doEscRightSquareBracketEsc(byte b) {
+ switch (b) {
+ case '\\':
+ doOSC();
+ break;
+
+ default:
+ // The ESC character was not followed by a \, so insert the ESC and
+ // the current character in arg buffer.
+ collectOSCArgs((byte) 0x1b);
+ collectOSCArgs(b);
+ continueSequence(ESC_RIGHT_SQUARE_BRACKET);
+ break;
+ }
+ }
+
+ private void doOSC() { // Operating System Controls
+ startTokenizingOSC();
+ int ps = nextOSCInt(';');
+ switch (ps) {
+ case 0: // Change icon name and window title to T
+ case 1: // Change icon name to T
+ case 2: // Change window title to T
+ changeTitle(ps, nextOSCString(-1));
+ break;
+ default:
+ unknownParameter(ps);
+ break;
+ }
+ finishSequence();
+ }
+
+ private void changeTitle(int parameter, String title) {
+ if (parameter == 0 || parameter == 2) {
+ mSession.setTitle(title);
+ }
+ }
+
+ private void blockClear(int sx, int sy, int w) {
+ blockClear(sx, sy, w, 1);
+ }
+
+ private void blockClear(int sx, int sy, int w, int h) {
+ mScreen.blockSet(sx, sy, w, h, ' ', getStyle());
+ }
+
+ private int getForeColor() {
+ return mForeColor;
+ }
+
+ private int getBackColor() {
+ return mBackColor;
+ }
+
+ private int getEffect() {
+ return mEffect;
+ }
+
+ private int getStyle() {
+ return TextStyle.encode(getForeColor(), getBackColor(), getEffect());
+ }
+
+ private void doSetMode(boolean newValue) {
+ int modeBit = getArg0(0);
+ switch (modeBit) {
+ case 4:
+ mInsertMode = newValue;
+ break;
+
+ default:
+ unknownParameter(modeBit);
+ break;
+ }
+ }
+
+ private void setHorizontalVerticalPosition() {
+
+ // Parameters are Row ; Column
+
+ setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
+ }
+
+ private void setCursorPosition(int x, int y) {
+ int effectiveTopMargin = 0;
+ int effectiveBottomMargin = mRows;
+ if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) {
+ effectiveTopMargin = mTopMargin;
+ effectiveBottomMargin = mBottomMargin;
+ }
+ int newRow =
+ Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y,
+ effectiveBottomMargin - 1));
+ int newCol = Math.max(0, Math.min(x, mColumns - 1));
+ setCursorRowCol(newRow, newCol);
+ }
+
+ private void sendDeviceAttributes() {
+ // This identifies us as a DEC vt100 with advanced
+ // video options. This is what the xterm terminal
+ // emulator sends.
+ byte[] attributes =
+ {
+ /* VT100 */
+ (byte) 27, (byte) '[', (byte) '?', (byte) '1',
+ (byte) ';', (byte) '2', (byte) 'c'
+
+ /* VT220
+ (byte) 27, (byte) '[', (byte) '?', (byte) '6',
+ (byte) '0', (byte) ';',
+ (byte) '1', (byte) ';',
+ (byte) '2', (byte) ';',
+ (byte) '6', (byte) ';',
+ (byte) '8', (byte) ';',
+ (byte) '9', (byte) ';',
+ (byte) '1', (byte) '5', (byte) ';',
+ (byte) 'c'
+ */
+ };
+
+ mSession.write(attributes, 0, attributes.length);
+ }
+
+ private void scroll() {
+ //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin);
+ mScrollCounter ++;
+ mScreen.scroll(mTopMargin, mBottomMargin, getStyle());
+ }
+
+ /**
+ * Process the next ASCII character of a parameter.
+ *
+ * @param b The next ASCII character of the paramater sequence.
+ */
+ private void parseArg(byte b) {
+ if (b >= '0' && b <= '9') {
+ if (mArgIndex < mArgs.length) {
+ int oldValue = mArgs[mArgIndex];
+ int thisDigit = b - '0';
+ int value;
+ if (oldValue >= 0) {
+ value = oldValue * 10 + thisDigit;
+ } else {
+ value = thisDigit;
+ }
+ mArgs[mArgIndex] = value;
+ }
+ continueSequence();
+ } else if (b == ';') {
+ if (mArgIndex < mArgs.length) {
+ mArgIndex++;
+ }
+ continueSequence();
+ } else {
+ unknownSequence(b);
+ }
+ }
+
+ private int getArg0(int defaultValue) {
+ return getArg(0, defaultValue, true);
+ }
+
+ private int getArg1(int defaultValue) {
+ return getArg(1, defaultValue, true);
+ }
+
+ private int getArg(int index, int defaultValue,
+ boolean treatZeroAsDefault) {
+ int result = mArgs[index];
+ if (result < 0 || (result == 0 && treatZeroAsDefault)) {
+ result = defaultValue;
+ }
+ return result;
+ }
+
+ private void startCollectingOSCArgs() {
+ mOSCArgLength = 0;
+ }
+
+ private void collectOSCArgs(byte b) {
+ if (mOSCArgLength < MAX_OSC_STRING_LENGTH) {
+ mOSCArg[mOSCArgLength++] = b;
+ continueSequence();
+ } else {
+ unknownSequence(b);
+ }
+ }
+
+ private void startTokenizingOSC() {
+ mOSCArgTokenizerIndex = 0;
+ }
+
+ private String nextOSCString(int delimiter) {
+ int start = mOSCArgTokenizerIndex;
+ int end = start;
+ while (mOSCArgTokenizerIndex < mOSCArgLength) {
+ byte b = mOSCArg[mOSCArgTokenizerIndex++];
+ if ((int) b == delimiter) {
+ break;
+ }
+ end++;
+ }
+ if (start == end) {
+ return "";
+ }
+ try {
+ return new String(mOSCArg, start, end-start, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ return new String(mOSCArg, start, end-start);
+ }
+ }
+
+ private int nextOSCInt(int delimiter) {
+ int value = -1;
+ while (mOSCArgTokenizerIndex < mOSCArgLength) {
+ byte b = mOSCArg[mOSCArgTokenizerIndex++];
+ if ((int) b == delimiter) {
+ break;
+ } else if (b >= '0' && b <= '9') {
+ if (value < 0) {
+ value = 0;
+ }
+ value = value * 10 + b - '0';
+ } else {
+ unknownSequence(b);
+ }
+ }
+ return value;
+ }
+
+ private void unimplementedSequence(byte b) {
+ if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ logError("unimplemented", b);
+ }
+ finishSequence();
+ }
+
+ private void unknownSequence(byte b) {
+ if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ logError("unknown", b);
+ }
+ finishSequence();
+ }
+
+ private void unknownParameter(int parameter) {
+ if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ StringBuilder buf = new StringBuilder();
+ buf.append("Unknown parameter");
+ buf.append(parameter);
+ logError(buf.toString());
+ }
+ }
+
+ private void logError(String errorType, byte b) {
+ if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ StringBuilder buf = new StringBuilder();
+ buf.append(errorType);
+ buf.append(" sequence ");
+ buf.append(" EscapeState: ");
+ buf.append(mEscapeState);
+ buf.append(" char: '");
+ buf.append((char) b);
+ buf.append("' (");
+ buf.append(b);
+ buf.append(")");
+ boolean firstArg = true;
+ for (int i = 0; i <= mArgIndex; i++) {
+ int value = mArgs[i];
+ if (value >= 0) {
+ if (firstArg) {
+ firstArg = false;
+ buf.append("args = ");
+ }
+ buf.append(String.format("%d; ", value));
+ }
+ }
+ logError(buf.toString());
+ }
+ }
+
+ private void logError(String error) {
+ if (EmulatorDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+ Log.e(EmulatorDebug.LOG_TAG, error);
+ }
+ finishSequence();
+ }
+
+ private void finishSequence() {
+ mEscapeState = ESC_NONE;
+ }
+
+ private boolean autoWrapEnabled() {
+ return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0;
+ }
+
+ /**
+ * Send a Unicode code point to the screen.
+ *
+ * @param c The code point of the character to display
+ * @param foreColor The foreground color of the character
+ * @param backColor The background color of the character
+ */
+ private void emit(int c, int style) {
+ boolean autoWrap = autoWrapEnabled();
+ int width = UnicodeTranscript.charWidth(c);
+
+ if (autoWrap) {
+ if (mCursorCol == mColumns - 1 && (mAboutToAutoWrap || width == 2)) {
+ mScreen.setLineWrap(mCursorRow);
+ mCursorCol = 0;
+ mJustWrapped = true;
+ if (mCursorRow + 1 < mBottomMargin) {
+ mCursorRow++;
+ } else {
+ scroll();
+ }
+ }
+ }
+
+ if (mInsertMode & width != 0) { // Move character to right one space
+ int destCol = mCursorCol + width;
+ if (destCol < mColumns) {
+ mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol,
+ 1, destCol, mCursorRow);
+ }
+ }
+
+ if (width == 0) {
+ // Combining character -- store along with character it modifies
+ if (mJustWrapped) {
+ mScreen.set(mColumns - mLastEmittedCharWidth, mCursorRow - 1, c, style);
+ } else {
+ mScreen.set(mCursorCol - mLastEmittedCharWidth, mCursorRow, c, style);
+ }
+ } else {
+ mScreen.set(mCursorCol, mCursorRow, c, style);
+ mJustWrapped = false;
+ }
+
+ if (autoWrap) {
+ mAboutToAutoWrap = (mCursorCol == mColumns - 1);
+
+ //Force line-wrap flag to trigger even for lines being typed
+ if(mAboutToAutoWrap)
+ mScreen.setLineWrap(mCursorRow);
+ }
+
+ mCursorCol = Math.min(mCursorCol + width, mColumns - 1);
+ if (width > 0) {
+ mLastEmittedCharWidth = width;
+ }
+ }
+
+ private void emit(int c) {
+ emit(c, getStyle());
+ }
+
+ private void emit(byte b) {
+ if (mUseAlternateCharSet && b < 128) {
+ emit((int) mSpecialGraphicsCharMap[b]);
+ } else {
+ emit((int) b);
+ }
+ }
+
+ /**
+ * Send a UTF-16 char or surrogate pair to the screen.
+ *
+ * @param c A char[2] containing either a single UTF-16 char or a surrogate pair to be sent to the screen.
+ */
+ private void emit(char[] c) {
+ if (Character.isHighSurrogate(c[0])) {
+ emit(Character.toCodePoint(c[0], c[1]));
+ } else {
+ emit((int) c[0]);
+ }
+ }
+
+ /**
+ * Send an array of UTF-16 chars to the screen.
+ *
+ * @param c A char[] array whose contents are to be sent to the screen.
+ */
+ private void emit(char[] c, int offset, int length, int style) {
+ for (int i = offset; i < length; ++i) {
+ if (c[i] == 0) {
+ break;
+ }
+ if (Character.isHighSurrogate(c[i])) {
+ emit(Character.toCodePoint(c[i], c[i+1]), style);
+ ++i;
+ } else {
+ emit((int) c[i], style);
+ }
+ }
+ }
+
+ private void setCursorRow(int row) {
+ mCursorRow = row;
+ mAboutToAutoWrap = false;
+ }
+
+ private void setCursorCol(int col) {
+ mCursorCol = col;
+ mAboutToAutoWrap = false;
+ }
+
+ private void setCursorRowCol(int row, int col) {
+ mCursorRow = Math.min(row, mRows-1);
+ mCursorCol = Math.min(col, mColumns-1);
+ mAboutToAutoWrap = false;
+ }
+
+ public int getScrollCounter() {
+ return mScrollCounter;
+ }
+
+ public void clearScrollCounter() {
+ mScrollCounter = 0;
+ }
+
+ /**
+ * Reset the terminal emulator to its initial state.
+ */
+ public void reset() {
+ mCursorRow = 0;
+ mCursorCol = 0;
+ mArgIndex = 0;
+ mContinueSequence = false;
+ mEscapeState = ESC_NONE;
+ mSavedCursorRow = 0;
+ mSavedCursorCol = 0;
+ mSavedEffect = 0;
+ mSavedDecFlags_DECSC_DECRC = 0;
+ mDecFlags = 0;
+ if (DEFAULT_TO_AUTOWRAP_ENABLED) {
+ mDecFlags |= K_WRAPAROUND_MODE_MASK;
+ }
+ mDecFlags |= K_SHOW_CURSOR_MASK;
+ mSavedDecFlags = 0;
+ mInsertMode = false;
+ mTopMargin = 0;
+ mBottomMargin = mRows;
+ mAboutToAutoWrap = false;
+ mForeColor = mDefaultForeColor;
+ mBackColor = mDefaultBackColor;
+ mbKeypadApplicationMode = false;
+ mAlternateCharSet = false;
+ mCharSet[0] = CHAR_SET_ASCII;
+ mCharSet[1] = CHAR_SET_SPECIAL_GRAPHICS;
+ computeEffectiveCharSet();
+ // mProcessedCharCount is preserved unchanged.
+ setDefaultTabStops();
+ blockClear(0, 0, mColumns, mRows);
+
+ setUTF8Mode(mDefaultUTF8Mode);
+ mUTF8EscapeUsed = false;
+ mUTF8ToFollow = 0;
+ mUTF8ByteBuffer.clear();
+ mInputCharBuffer.clear();
+ }
+
+ public void setDefaultUTF8Mode(boolean defaultToUTF8Mode) {
+ mDefaultUTF8Mode = defaultToUTF8Mode;
+ if (!mUTF8EscapeUsed) {
+ setUTF8Mode(defaultToUTF8Mode);
+ }
+ }
+
+ public void setUTF8Mode(boolean utf8Mode) {
+ if (utf8Mode && !mUTF8Mode) {
+ mUTF8ToFollow = 0;
+ mUTF8ByteBuffer.clear();
+ mInputCharBuffer.clear();
+ }
+ mUTF8Mode = utf8Mode;
+ if (mUTF8ModeNotify != null) {
+ mUTF8ModeNotify.onUpdate();
+ }
+ }
+
+ public boolean getUTF8Mode() {
+ return mUTF8Mode;
+ }
+
+ public void setUTF8ModeUpdateCallback(UpdateCallback utf8ModeNotify) {
+ mUTF8ModeNotify = utf8ModeNotify;
+ }
+
+ public void setColorScheme(ColorScheme scheme) {
+ mDefaultForeColor = TextStyle.ciForeground;
+ mDefaultBackColor = TextStyle.ciBackground;
+ mMainBuffer.setColorScheme(scheme);
+ if (mAltBuffer != null) {
+ mAltBuffer.setColorScheme(scheme);
+ }
+ }
+
+ public String getSelectedText(int x1, int y1, int x2, int y2) {
+ return mScreen.getSelectedText(x1, y1, x2, y2);
+ }
+
+ public void finish() {
+ if (mAltBuffer != null) {
+ mAltBuffer.finish();
+ mAltBuffer = null;
+ }
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextRenderer.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextRenderer.java
new file mode 100644
index 000000000..39c615da8
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextRenderer.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.emulatorview;
+
+import android.graphics.Canvas;
+
+/**
+ * Text renderer interface
+ */
+
+interface TextRenderer {
+ public static final int MODE_OFF = 0;
+ public static final int MODE_ON = 1;
+ public static final int MODE_LOCKED = 2;
+ public static final int MODE_MASK = 3;
+
+ public static final int MODE_SHIFT_SHIFT = 0;
+ public static final int MODE_ALT_SHIFT = 2;
+ public static final int MODE_CTRL_SHIFT = 4;
+ public static final int MODE_FN_SHIFT = 6;
+
+ void setReverseVideo(boolean reverseVideo);
+ float getCharacterWidth();
+ int getCharacterHeight();
+ /** @return pixels above top row of text to avoid looking cramped. */
+ int getTopMargin();
+ /**
+ * Draw a run of text
+ * @param canvas The canvas to draw into.
+ * @param x Canvas coordinate of the left edge of the whole line.
+ * @param y Canvas coordinate of the bottom edge of the whole line.
+ * @param lineOffset The screen character offset of this text run (0..length of line)
+ * @param runWidth
+ * @param text
+ * @param index
+ * @param count
+ * @param selectionStyle True to draw the text using the "selected" style (for clipboard copy)
+ * @param textStyle
+ * @param cursorOffset The screen character offset of the cursor (or -1 if not on this line.)
+ * @param cursorIndex The index of the cursor in text chars.
+ * @param cursorIncr The width of the cursor in text chars. (1 or 2)
+ * @param cursorWidth The width of the cursor in screen columns (1 or 2)
+ * @param cursorMode The cursor mode (used to show state of shift/control/alt/fn locks.
+ */
+ void drawTextRun(Canvas canvas, float x, float y,
+ int lineOffset, int runWidth, char[] text,
+ int index, int count, boolean selectionStyle, int textStyle,
+ int cursorOffset, int cursorIndex, int cursorIncr, int cursorWidth, int cursorMode);
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextStyle.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextStyle.java
new file mode 100644
index 000000000..bf16e4356
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextStyle.java
@@ -0,0 +1,44 @@
+package jackpal.androidterm.emulatorview;
+
+final class TextStyle {
+ // Effect bitmasks:
+ final static int fxNormal = 0;
+ final static int fxBold = 1; // Originally Bright
+ //final static int fxFaint = 2;
+ final static int fxItalic = 1 << 1;
+ final static int fxUnderline = 1 << 2;
+ final static int fxBlink = 1 << 3;
+ final static int fxInverse = 1 << 4;
+ final static int fxInvisible = 1 << 5;
+
+ // Special color indices
+ final static int ciForeground = 256; // VT100 text foreground color
+ final static int ciBackground = 257; // VT100 text background color
+ final static int ciCursorForeground = 258; // VT100 text cursor foreground color
+ final static int ciCursorBackground = 259; // VT100 text cursor background color
+
+ final static int ciColorLength = ciCursorBackground + 1;
+
+ final static int kNormalTextStyle = encode(ciForeground, ciBackground, fxNormal);
+
+ static int encode(int foreColor, int backColor, int effect) {
+ return ((effect & 0x3f) << 18) | ((foreColor & 0x1ff) << 9) | (backColor & 0x1ff);
+ }
+
+ static int decodeForeColor(int encodedColor) {
+ return (encodedColor >> 9) & 0x1ff;
+ }
+
+ static int decodeBackColor(int encodedColor) {
+ return encodedColor & 0x1ff;
+ }
+
+ static int decodeEffect(int encodedColor) {
+ return (encodedColor >> 18) & 0x3f;
+ }
+
+ private TextStyle() {
+ // Prevent instantiation
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TranscriptScreen.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TranscriptScreen.java
new file mode 100644
index 000000000..e1dc3c02b
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TranscriptScreen.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.emulatorview;
+
+import java.util.Arrays;
+import android.graphics.Canvas;
+
+/**
+ * A TranscriptScreen is a screen that remembers data that's been scrolled. The
+ * old data is stored in a ring buffer to minimize the amount of copying that
+ * needs to be done. The transcript does its own drawing, to avoid having to
+ * expose its internal data structures.
+ */
+class TranscriptScreen implements Screen {
+ /**
+ * The width of the transcript, in characters. Fixed at initialization.
+ */
+ private int mColumns;
+
+ /**
+ * The total number of rows in the transcript and the screen. Fixed at
+ * initialization.
+ */
+ private int mTotalRows;
+
+ /**
+ * The number of rows in the screen.
+ */
+ private int mScreenRows;
+
+ private UnicodeTranscript mData;
+
+ /**
+ * Create a transcript screen.
+ *
+ * @param columns the width of the screen in characters.
+ * @param totalRows the height of the entire text area, in rows of text.
+ * @param screenRows the height of just the screen, not including the
+ * transcript that holds lines that have scrolled off the top of the
+ * screen.
+ */
+ public TranscriptScreen(int columns, int totalRows, int screenRows,
+ ColorScheme scheme) {
+ init(columns, totalRows, screenRows, TextStyle.kNormalTextStyle);
+ }
+
+ private void init(int columns, int totalRows, int screenRows, int style) {
+ mColumns = columns;
+ mTotalRows = totalRows;
+ mScreenRows = screenRows;
+
+ mData = new UnicodeTranscript(columns, totalRows, screenRows, style);
+ mData.blockSet(0, 0, mColumns, mScreenRows, ' ', style);
+ }
+
+ public void setColorScheme(ColorScheme scheme) {
+ mData.setDefaultStyle(TextStyle.kNormalTextStyle);
+ }
+
+ public void finish() {
+ /*
+ * The Android InputMethodService will sometimes hold a reference to
+ * us for a while after the activity closes, which is expensive because
+ * it means holding on to the now-useless mData array. Explicitly
+ * get rid of our references to this data to help keep the amount of
+ * memory being leaked down.
+ */
+ mData = null;
+ }
+
+ public void setLineWrap(int row) {
+ mData.setLineWrap(row);
+ }
+
+ /**
+ * Store a Unicode code point into the screen at location (x, y)
+ *
+ * @param x X coordinate (also known as column)
+ * @param y Y coordinate (also known as row)
+ * @param codePoint Unicode codepoint to store
+ * @param foreColor the foreground color
+ * @param backColor the background color
+ */
+ public void set(int x, int y, int codePoint, int style) {
+ mData.setChar(x, y, codePoint, style);
+ }
+
+ public void set(int x, int y, byte b, int style) {
+ mData.setChar(x, y, b, style);
+ }
+
+ /**
+ * Scroll the screen down one line. To scroll the whole screen of a 24 line
+ * screen, the arguments would be (0, 24).
+ *
+ * @param topMargin First line that is scrolled.
+ * @param bottomMargin One line after the last line that is scrolled.
+ * @param style the style for the newly exposed line.
+ */
+ public void scroll(int topMargin, int bottomMargin, int style) {
+ mData.scroll(topMargin, bottomMargin, style);
+ }
+
+ /**
+ * Block copy characters from one position in the screen to another. The two
+ * positions can overlap. All characters of the source and destination must
+ * be within the bounds of the screen, or else an InvalidParemeterException
+ * will be thrown.
+ *
+ * @param sx source X coordinate
+ * @param sy source Y coordinate
+ * @param w width
+ * @param h height
+ * @param dx destination X coordinate
+ * @param dy destination Y coordinate
+ */
+ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
+ mData.blockCopy(sx, sy, w, h, dx, dy);
+ }
+
+ /**
+ * Block set characters. All characters must be within the bounds of the
+ * screen, or else and InvalidParemeterException will be thrown. Typically
+ * this is called with a "val" argument of 32 to clear a block of
+ * characters.
+ *
+ * @param sx source X
+ * @param sy source Y
+ * @param w width
+ * @param h height
+ * @param val value to set.
+ */
+ public void blockSet(int sx, int sy, int w, int h, int val,
+ int style) {
+ mData.blockSet(sx, sy, w, h, val, style);
+ }
+
+ /**
+ * Draw a row of text. Out-of-bounds rows are blank, not errors.
+ *
+ * @param row The row of text to draw.
+ * @param canvas The canvas to draw to.
+ * @param x The x coordinate origin of the drawing
+ * @param y The y coordinate origin of the drawing
+ * @param renderer The renderer to use to draw the text
+ * @param cx the cursor X coordinate, -1 means don't draw it
+ * @param selx1 the text selection start X coordinate
+ * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection
+ * @param imeText current IME text, to be rendered at cursor
+ * @param cursorMode the cursor mode. See TextRenderer.
+ */
+ public final void drawText(int row, Canvas canvas, float x, float y,
+ TextRenderer renderer, int cx, int selx1, int selx2, String imeText, int cursorMode) {
+ char[] line;
+ StyleRow color;
+ int cursorWidth = 1;
+ try {
+ line = mData.getLine(row);
+ color = mData.getLineColor(row);
+ } catch (IllegalArgumentException e) {
+ // Out-of-bounds rows are blank.
+ return;
+ } catch (NullPointerException e) {
+ // Attempt to draw on a finished transcript
+ // XXX Figure out why this happens on Honeycomb
+ return;
+ }
+ int defaultStyle = mData.getDefaultStyle();
+
+ if (line == null) {
+ // Line is blank.
+ if (selx1 != selx2) {
+ // We need to draw a selection
+ char[] blank = new char[selx2-selx1];
+ Arrays.fill(blank, ' ');
+ renderer.drawTextRun(canvas, x, y, selx1, selx2-selx1,
+ blank, 0, 1, true, defaultStyle,
+ cx, 0, 1, 1, cursorMode);
+ }
+ if (cx != -1) {
+ char[] blank = new char[1];
+ Arrays.fill(blank, ' ');
+ // We need to draw the cursor
+ renderer.drawTextRun(canvas, x, y, cx, 1,
+ blank, 0, 1, true, defaultStyle,
+ cx, 0, 1, 1, cursorMode);
+ }
+
+ return;
+ }
+
+ int columns = mColumns;
+ int lineLen = line.length;
+ int lastStyle = 0;
+ boolean lastSelectionStyle = false;
+ int runWidth = 0;
+ int lastRunStart = -1;
+ int lastRunStartIndex = -1;
+ boolean forceFlushRun = false;
+ int column = 0;
+ int nextColumn = 0;
+ int displayCharWidth = 0;
+ int index = 0;
+ int cursorIndex = 0;
+ int cursorIncr = 0;
+ while (column < columns && index < lineLen && line[index] != '\0') {
+ int incr = 1;
+ int width;
+ if (Character.isHighSurrogate(line[index])) {
+ width = UnicodeTranscript.charWidth(line, index);
+ incr++;
+ } else {
+ width = UnicodeTranscript.charWidth(line[index]);
+ }
+ if (width > 0) {
+ // We've moved on to the next column
+ column = nextColumn;
+ displayCharWidth = width;
+ }
+ int style = color.get(column);
+ boolean selectionStyle = false;
+ if ((column >= selx1 || (displayCharWidth == 2 && column == selx1 - 1)) &&
+ column <= selx2) {
+ // Draw selection:
+ selectionStyle = true;
+ }
+ if (style != lastStyle
+ || selectionStyle != lastSelectionStyle
+ || (width > 0 && forceFlushRun)) {
+ if (lastRunStart >= 0) {
+ renderer.drawTextRun(canvas, x, y, lastRunStart, runWidth,
+ line,
+ lastRunStartIndex, index - lastRunStartIndex,
+ lastSelectionStyle, lastStyle,
+ cx, cursorIndex, cursorIncr, cursorWidth, cursorMode);
+ }
+ lastStyle = style;
+ lastSelectionStyle = selectionStyle;
+ runWidth = 0;
+ lastRunStart = column;
+ lastRunStartIndex = index;
+ forceFlushRun = false;
+ }
+ if (cx == column) {
+ if (width > 0) {
+ cursorIndex = index;
+ cursorIncr = incr;
+ cursorWidth = width;
+ } else {
+ // Combining char attaching to the char under the cursor
+ cursorIncr += incr;
+ }
+ }
+ runWidth += width;
+ nextColumn += width;
+ index += incr;
+ if (width > 1) {
+ /* We cannot draw two or more East Asian wide characters in the
+ same run, because we need to make each wide character take
+ up two columns, which may not match the font's idea of the
+ character width */
+ forceFlushRun = true;
+ }
+ }
+ if (lastRunStart >= 0) {
+ renderer.drawTextRun(canvas, x, y, lastRunStart, runWidth,
+ line,
+ lastRunStartIndex, index - lastRunStartIndex,
+ lastSelectionStyle, lastStyle,
+ cx, cursorIndex, cursorIncr, cursorWidth, cursorMode);
+ }
+
+ if (cx >= 0 && imeText.length() > 0) {
+ int imeLength = Math.min(columns, imeText.length());
+ int imeOffset = imeText.length() - imeLength;
+ int imePosition = Math.min(cx, columns - imeLength);
+ renderer.drawTextRun(canvas, x, y, imePosition, imeLength, imeText.toCharArray(),
+ imeOffset, imeLength, true, TextStyle.encode(0x0f, 0x00, TextStyle.fxNormal),
+ -1, 0, 0, 0, 0);
+ }
+ }
+
+ /**
+ * Get the count of active rows.
+ *
+ * @return the count of active rows.
+ */
+ public int getActiveRows() {
+ return mData.getActiveRows();
+ }
+
+ /**
+ * Get the count of active transcript rows.
+ *
+ * @return the count of active transcript rows.
+ */
+ public int getActiveTranscriptRows() {
+ return mData.getActiveTranscriptRows();
+ }
+
+ public String getTranscriptText() {
+ return internalGetTranscriptText(null, 0, -mData.getActiveTranscriptRows(), mColumns, mScreenRows);
+ }
+
+ public String getTranscriptText(GrowableIntArray colors) {
+ return internalGetTranscriptText(colors, 0, -mData.getActiveTranscriptRows(), mColumns, mScreenRows);
+ }
+
+ public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
+ return internalGetTranscriptText(null, selX1, selY1, selX2, selY2);
+ }
+
+ public String getSelectedText(GrowableIntArray colors, int selX1, int selY1, int selX2, int selY2) {
+ return internalGetTranscriptText(colors, selX1, selY1, selX2, selY2);
+ }
+
+ private String internalGetTranscriptText(GrowableIntArray colors, int selX1, int selY1, int selX2, int selY2) {
+ StringBuilder builder = new StringBuilder();
+ UnicodeTranscript data = mData;
+ int columns = mColumns;
+ char[] line;
+ StyleRow rowColorBuffer = null;
+ if (selY1 < -data.getActiveTranscriptRows()) {
+ selY1 = -data.getActiveTranscriptRows();
+ }
+ if (selY2 >= mScreenRows) {
+ selY2 = mScreenRows - 1;
+ }
+ for (int row = selY1; row <= selY2; row++) {
+ int x1 = 0;
+ int x2;
+ if ( row == selY1 ) {
+ x1 = selX1;
+ }
+ if ( row == selY2 ) {
+ x2 = selX2 + 1;
+ if (x2 > columns) {
+ x2 = columns;
+ }
+ } else {
+ x2 = columns;
+ }
+ line = data.getLine(row, x1, x2);
+ if (colors != null) {
+ rowColorBuffer = data.getLineColor(row, x1, x2);
+ }
+ if (line == null) {
+ if (!data.getLineWrap(row) && row < selY2 && row < mScreenRows - 1) {
+ builder.append('\n');
+ if (colors != null) {
+ colors.append(0);
+ }
+ }
+ continue;
+ }
+ int defaultColor = mData.getDefaultStyle();
+ int lastPrintingChar = -1;
+ int lineLen = line.length;
+ int i;
+ int column = 0;
+ for (i = 0; i < lineLen; ++i) {
+ char c = line[i];
+ if (c == 0) {
+ break;
+ }
+
+ int style = defaultColor;
+ try {
+ if (rowColorBuffer != null) {
+ style = rowColorBuffer.get(column);
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // XXX This probably shouldn't happen ...
+ style = defaultColor;
+ }
+
+ if (c != ' ' || style != defaultColor) {
+ lastPrintingChar = i;
+ }
+ if (!Character.isLowSurrogate(c)) {
+ column += UnicodeTranscript.charWidth(line, i);
+ }
+ }
+ if (data.getLineWrap(row) && lastPrintingChar > -1 && x2 == columns) {
+ // If the line was wrapped, we shouldn't lose trailing space
+ lastPrintingChar = i - 1;
+ }
+ builder.append(line, 0, lastPrintingChar + 1);
+ if (colors != null) {
+ if (rowColorBuffer != null) {
+ column = 0;
+ for (int j = 0; j <= lastPrintingChar; ++j) {
+ colors.append(rowColorBuffer.get(column));
+ column += UnicodeTranscript.charWidth(line, j);
+ if (Character.isHighSurrogate(line[j])) {
+ ++j;
+ }
+ }
+ } else {
+ for (int j = 0; j <= lastPrintingChar; ++j) {
+ colors.append(defaultColor);
+ char c = line[j];
+ if (Character.isHighSurrogate(c)) {
+ ++j;
+ }
+ }
+ }
+ }
+ if (!data.getLineWrap(row) && row < selY2 && row < mScreenRows - 1) {
+ builder.append('\n');
+ if (colors != null) {
+ colors.append((char) 0);
+ }
+ }
+ }
+ return builder.toString();
+ }
+
+ public boolean fastResize(int columns, int rows, int[] cursor) {
+ if (mData == null) {
+ // XXX Trying to resize a finished TranscriptScreen?
+ return true;
+ }
+ if (mData.resize(columns, rows, cursor)) {
+ mColumns = columns;
+ mScreenRows = rows;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void resize(int columns, int rows, int style) {
+ // Ensure backing store will be large enough to hold the whole screen
+ if (rows > mTotalRows) {
+ mTotalRows = rows;
+ }
+ init(columns, mTotalRows, rows, style);
+ }
+
+ /**
+ *
+ * Return the UnicodeTranscript line at this row index.
+ * @param row The row index to be queried
+ * @return The line of text at this row index
+ */
+ char[] getScriptLine(int row)
+ {
+ try
+ {
+ return mData.getLine(row);
+ }
+ catch (IllegalArgumentException e)
+ {
+ return null;
+ }
+ catch (NullPointerException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Get the line wrap status of the row provided.
+ * @param row The row to check for line-wrap status
+ * @return The line wrap status of the row provided
+ */
+ boolean getScriptLineWrap(int row)
+ {
+ return mData.getLineWrap(row);
+ }
+
+ /**
+ * Get whether the line at this index is "basic" (contains only BMP
+ * characters of width 1).
+ */
+ boolean isBasicLine(int row) {
+ if (mData != null) {
+ return mData.isBasicLine(row);
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/UnicodeTranscript.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/UnicodeTranscript.java
new file mode 100644
index 000000000..2b8fdfcbe
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/UnicodeTranscript.java
@@ -0,0 +1,1141 @@
+/*
+ * Copyright (C) 2011 Steven Luo
+ *
+ * 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 jackpal.androidterm.emulatorview;
+
+import android.util.Log;
+
+import jackpal.androidterm.emulatorview.compat.AndroidCharacterCompat;
+import jackpal.androidterm.emulatorview.compat.AndroidCompat;
+
+/**
+ * A backing store for a TranscriptScreen.
+ *
+ * The text is stored as a circular buffer of rows. There are two types of
+ * row:
+ * - "basic", which is a char[] array used to store lines which consist
+ * entirely of regular-width characters (no combining characters, zero-width
+ * characters, East Asian double-width characters, etc.) in the BMP; and
+ * - "full", which is a char[] array with extra trappings which can be used to
+ * store a line containing any valid Unicode sequence. An array of short[]
+ * is used to store the "offset" at which each column starts; for example,
+ * if column 20 starts at index 23 in the array, then mOffset[20] = 3.
+ *
+ * Style information is stored in a separate circular buffer of StyleRows.
+ *
+ * Rows are allocated on demand, when a character is first stored into them.
+ * A "basic" row is allocated unless the store which triggers the allocation
+ * requires a "full" row. "Basic" rows are converted to "full" rows when
+ * needed. There is no conversion in the other direction -- a "full" row
+ * stays that way even if it contains only regular-width BMP characters.
+ */
+class UnicodeTranscript {
+ private static final String TAG = "UnicodeTranscript";
+
+ private Object[] mLines;
+ private StyleRow[] mColor;
+ private boolean[] mLineWrap;
+ private int mTotalRows;
+ private int mScreenRows;
+ private int mColumns;
+ private int mActiveTranscriptRows = 0;
+ private int mDefaultStyle = 0;
+
+ private int mScreenFirstRow = 0;
+
+ private char[] tmpLine;
+ private StyleRow tmpColor;
+
+ public UnicodeTranscript(int columns, int totalRows, int screenRows, int defaultStyle) {
+ mColumns = columns;
+ mTotalRows = totalRows;
+ mScreenRows = screenRows;
+ mLines = new Object[totalRows];
+ mColor = new StyleRow[totalRows];
+ mLineWrap = new boolean[totalRows];
+ tmpColor = new StyleRow(defaultStyle, mColumns);
+
+ mDefaultStyle = defaultStyle;
+ }
+
+ public void setDefaultStyle(int defaultStyle) {
+ mDefaultStyle = defaultStyle;
+ }
+
+ public int getDefaultStyle() {
+ return mDefaultStyle;
+ }
+
+ public int getActiveTranscriptRows() {
+ return mActiveTranscriptRows;
+ }
+
+ public int getActiveRows() {
+ return mActiveTranscriptRows + mScreenRows;
+ }
+
+ /**
+ * Convert a row value from the public external coordinate system to our
+ * internal private coordinate system.
+ * External coordinate system:
+ * -mActiveTranscriptRows to mScreenRows-1, with the screen being
+ * 0..mScreenRows-1
+ * Internal coordinate system: the mScreenRows lines starting at
+ * mScreenFirstRow comprise the screen, while the mActiveTranscriptRows
+ * lines ending at mScreenRows-1 form the transcript (as a circular
+ * buffer).
+ *
+ * @param extRow a row in the external coordinate system.
+ * @return The row corresponding to the input argument in the private
+ * coordinate system.
+ */
+ private int externalToInternalRow(int extRow) {
+ if (extRow < -mActiveTranscriptRows || extRow > mScreenRows) {
+ String errorMessage = "externalToInternalRow "+ extRow +
+ " " + mScreenRows + " " + mActiveTranscriptRows;
+ Log.e(TAG, errorMessage);
+ throw new IllegalArgumentException(errorMessage);
+ }
+
+ if (extRow >= 0) {
+ return (mScreenFirstRow + extRow) % mTotalRows;
+ } else {
+ if (-extRow > mScreenFirstRow) {
+ return mTotalRows + mScreenFirstRow + extRow;
+ } else {
+ return mScreenFirstRow + extRow;
+ }
+ }
+ }
+
+ public void setLineWrap(int row) {
+ mLineWrap[externalToInternalRow(row)] = true;
+ }
+
+ public boolean getLineWrap(int row) {
+ return mLineWrap[externalToInternalRow(row)];
+ }
+
+ /**
+ * Resize the screen which this transcript backs. Currently, this
+ * only works if the number of columns does not change.
+ *
+ * @param newColumns The number of columns the screen should have.
+ * @param newRows The number of rows the screen should have.
+ * @param cursor An int[2] containing the current cursor location; if the
+ * resize succeeds, this will be updated with the new cursor
+ * location. If null, don't do cursor-position-dependent tasks such
+ * as trimming blank lines during the resize.
+ * @return Whether or not the resize succeeded. If the resize failed,
+ * the caller may "resize" the screen by copying out all the data
+ * and placing it into a new transcript of the correct size.
+ */
+ public boolean resize(int newColumns, int newRows, int[] cursor) {
+ if (newColumns != mColumns || newRows > mTotalRows) {
+ return false;
+ }
+
+ int screenRows = mScreenRows;
+ int activeTranscriptRows = mActiveTranscriptRows;
+ int shift = screenRows - newRows;
+ if (shift < -activeTranscriptRows) {
+ // We want to add blank lines at the bottom instead of at the top
+ Object[] lines = mLines;
+ Object[] color = mColor;
+ boolean[] lineWrap = mLineWrap;
+ int screenFirstRow = mScreenFirstRow;
+ int totalRows = mTotalRows;
+ for (int i = 0; i < activeTranscriptRows - shift; ++i) {
+ int index = (screenFirstRow + screenRows + i) % totalRows;
+ lines[index] = null;
+ color[index] = null;
+ lineWrap[index] = false;
+ }
+ shift = -activeTranscriptRows;
+ } else if (shift > 0 && cursor != null && cursor[1] != screenRows - 1) {
+ /* When shrinking the screen, we want to hide blank lines at the
+ bottom in preference to lines at the top of the screen */
+ Object[] lines = mLines;
+ for (int i = screenRows - 1; i > cursor[1]; --i) {
+ int index = externalToInternalRow(i);
+ if (lines[index] == null) {
+ // Line is blank
+ --shift;
+ if (shift == 0) {
+ break;
+ } else {
+ continue;
+ }
+ }
+
+ char[] line;
+ if (lines[index] instanceof char[]) {
+ line = (char[]) lines[index];
+ } else {
+ line = ((FullUnicodeLine) lines[index]).getLine();
+ }
+
+ int len = line.length;
+ int j;
+ for (j = 0; j < len; ++j) {
+ if (line[j] == 0) {
+ // We've reached the end of the line
+ j = len;
+ break;
+ } else if (line[j] != ' ') {
+ // Line is not blank
+ break;
+ }
+ }
+
+ if (j == len) {
+ // Line is blank
+ --shift;
+ if (shift == 0) {
+ break;
+ } else {
+ continue;
+ }
+ } else {
+ // Line not blank -- we keep it and everything above
+ break;
+ }
+ }
+ }
+
+ if (shift > 0 || (shift < 0 && mScreenFirstRow >= -shift)) {
+ // All we're doing is moving the top of the screen.
+ mScreenFirstRow = (mScreenFirstRow + shift) % mTotalRows;
+ } else if (shift < 0) {
+ // The new top of the screen wraps around the top of the array.
+ mScreenFirstRow = mTotalRows + mScreenFirstRow + shift;
+ }
+
+ if (mActiveTranscriptRows + shift < 0) {
+ mActiveTranscriptRows = 0;
+ } else {
+ mActiveTranscriptRows += shift;
+ }
+ if (cursor != null) {
+ cursor[1] -= shift;
+ }
+ mScreenRows = newRows;
+
+ return true;
+ }
+
+ /**
+ * Block copy lines and associated metadata from one location to another
+ * in the circular buffer, taking wraparound into account.
+ *
+ * @param src The first line to be copied.
+ * @param len The number of lines to be copied.
+ * @param shift The offset of the destination from the source.
+ */
+ private void blockCopyLines(int src, int len, int shift) {
+ int totalRows = mTotalRows;
+
+ int dst;
+ if (src + shift >= 0) {
+ dst = (src + shift) % totalRows;
+ } else {
+ dst = totalRows + src + shift;
+ }
+
+ if (src + len <= totalRows && dst + len <= totalRows) {
+ // Fast path -- no wraparound
+ System.arraycopy(mLines, src, mLines, dst, len);
+ System.arraycopy(mColor, src, mColor, dst, len);
+ System.arraycopy(mLineWrap, src, mLineWrap, dst, len);
+ return;
+ }
+
+ if (shift < 0) {
+ // Do the copy from top to bottom
+ for (int i = 0; i < len; ++i) {
+ mLines[(dst + i) % totalRows] = mLines[(src + i) % totalRows];
+ mColor[(dst + i) % totalRows] = mColor[(src + i) % totalRows];
+ mLineWrap[(dst + i) % totalRows] = mLineWrap[(src + i) % totalRows];
+ }
+ } else {
+ // Do the copy from bottom to top
+ for (int i = len - 1; i >= 0; --i) {
+ mLines[(dst + i) % totalRows] = mLines[(src + i) % totalRows];
+ mColor[(dst + i) % totalRows] = mColor[(src + i) % totalRows];
+ mLineWrap[(dst + i) % totalRows] = mLineWrap[(src + i) % totalRows];
+ }
+ }
+ }
+
+ /**
+ * Scroll the screen down one line. To scroll the whole screen of a 24 line
+ * screen, the arguments would be (0, 24).
+ *
+ * @param topMargin First line that is scrolled.
+ * @param bottomMargin One line after the last line that is scrolled.
+ * @param style the style for the newly exposed line.
+ */
+ public void scroll(int topMargin, int bottomMargin, int style) {
+ // Separate out reasons so that stack crawls help us
+ // figure out which condition was violated.
+ if (topMargin > bottomMargin - 1) {
+ throw new IllegalArgumentException();
+ }
+
+ if (topMargin < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ if (bottomMargin > mScreenRows) {
+ throw new IllegalArgumentException();
+ }
+
+ int screenRows = mScreenRows;
+ int totalRows = mTotalRows;
+
+ if (topMargin == 0 && bottomMargin == screenRows) {
+ // Fast path -- scroll the entire screen
+ mScreenFirstRow = (mScreenFirstRow + 1) % totalRows;
+ if (mActiveTranscriptRows < totalRows - screenRows) {
+ ++mActiveTranscriptRows;
+ }
+
+ // Blank the bottom margin
+ int blankRow = externalToInternalRow(bottomMargin - 1);
+ mLines[blankRow] = null;
+ mColor[blankRow] = new StyleRow(style, mColumns);
+ mLineWrap[blankRow] = false;
+
+ return;
+ }
+
+ int screenFirstRow = mScreenFirstRow;
+ int topMarginInt = externalToInternalRow(topMargin);
+ int bottomMarginInt = externalToInternalRow(bottomMargin);
+
+ /* Save the scrolled line, move the lines above it on the screen down
+ one line, move the lines on screen below the bottom margin down
+ one line, then insert the scrolled line into the transcript */
+ Object[] lines = mLines;
+ StyleRow[] color = mColor;
+ boolean[] lineWrap = mLineWrap;
+ Object scrollLine = lines[topMarginInt];
+ StyleRow scrollColor = color[topMarginInt];
+ boolean scrollLineWrap = lineWrap[topMarginInt];
+ blockCopyLines(screenFirstRow, topMargin, 1);
+ blockCopyLines(bottomMarginInt, screenRows - bottomMargin, 1);
+ lines[screenFirstRow] = scrollLine;
+ color[screenFirstRow] = scrollColor;
+ lineWrap[screenFirstRow] = scrollLineWrap;
+
+ // Update the screen location
+ mScreenFirstRow = (screenFirstRow + 1) % totalRows;
+ if (mActiveTranscriptRows < totalRows - screenRows) {
+ ++mActiveTranscriptRows;
+ }
+
+ // Blank the bottom margin
+ int blankRow = externalToInternalRow(bottomMargin - 1);
+ lines[blankRow] = null;
+ color[blankRow] = new StyleRow(style, mColumns);
+ lineWrap[blankRow] = false;
+
+ return;
+ }
+
+ /**
+ * Block copy characters from one position in the screen to another. The two
+ * positions can overlap. All characters of the source and destination must
+ * be within the bounds of the screen, or else an InvalidParameterException
+ * will be thrown.
+ *
+ * @param sx source X coordinate
+ * @param sy source Y coordinate
+ * @param w width
+ * @param h height
+ * @param dx destination X coordinate
+ * @param dy destination Y coordinate
+ */
+ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
+ if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows
+ || dx < 0 || dx + w > mColumns || dy < 0
+ || dy + h > mScreenRows) {
+ throw new IllegalArgumentException();
+ }
+ Object[] lines = mLines;
+ StyleRow[] color = mColor;
+ if (sy > dy) {
+ // Move in increasing order
+ for (int y = 0; y < h; y++) {
+ int srcRow = externalToInternalRow(sy + y);
+ int dstRow = externalToInternalRow(dy + y);
+ if (lines[srcRow] instanceof char[] && lines[dstRow] instanceof char[]) {
+ System.arraycopy(lines[srcRow], sx, lines[dstRow], dx, w);
+ } else {
+ // XXX There has to be a faster way to do this ...
+ int extDstRow = dy + y;
+ char[] tmp = getLine(sy + y, sx, sx + w, true);
+ if (tmp == null) {
+ // Source line was blank
+ blockSet(dx, extDstRow, w, 1, ' ', mDefaultStyle);
+ continue;
+ }
+ char cHigh = 0;
+ int x = 0;
+ int columns = mColumns;
+ for (int i = 0; i < tmp.length; ++i) {
+ if (tmp[i] == 0 || dx + x >= columns) {
+ break;
+ }
+ if (Character.isHighSurrogate(tmp[i])) {
+ cHigh = tmp[i];
+ continue;
+ } else if (Character.isLowSurrogate(tmp[i])) {
+ int codePoint = Character.toCodePoint(cHigh, tmp[i]);
+ setChar(dx + x, extDstRow, codePoint);
+ x += charWidth(codePoint);
+ } else {
+ setChar(dx + x, extDstRow, tmp[i]);
+ x += charWidth(tmp[i]);
+ }
+ }
+ }
+ color[srcRow].copy(sx, color[dstRow], dx, w);
+ }
+ } else {
+ // Move in decreasing order
+ for (int y = 0; y < h; y++) {
+ int y2 = h - (y + 1);
+ int srcRow = externalToInternalRow(sy + y2);
+ int dstRow = externalToInternalRow(dy + y2);
+ if (lines[srcRow] instanceof char[] && lines[dstRow] instanceof char[]) {
+ System.arraycopy(lines[srcRow], sx, lines[dstRow], dx, w);
+ } else {
+ int extDstRow = dy + y2;
+ char[] tmp = getLine(sy + y2, sx, sx + w, true);
+ if (tmp == null) {
+ // Source line was blank
+ blockSet(dx, extDstRow, w, 1, ' ', mDefaultStyle);
+ continue;
+ }
+ char cHigh = 0;
+ int x = 0;
+ int columns = mColumns;
+ for (int i = 0; i < tmp.length; ++i) {
+ if (tmp[i] == 0 || dx + x >= columns) {
+ break;
+ }
+ if (Character.isHighSurrogate(tmp[i])) {
+ cHigh = tmp[i];
+ continue;
+ } else if (Character.isLowSurrogate(tmp[i])) {
+ int codePoint = Character.toCodePoint(cHigh, tmp[i]);
+ setChar(dx + x, extDstRow, codePoint);
+ x += charWidth(codePoint);
+ } else {
+ setChar(dx + x, extDstRow, tmp[i]);
+ x += charWidth(tmp[i]);
+ }
+ }
+ }
+ color[srcRow].copy(sx, color[dstRow], dx, w);
+ }
+ }
+ }
+
+ /**
+ * Block set characters. All characters must be within the bounds of the
+ * screen, or else and InvalidParemeterException will be thrown. Typically
+ * this is called with a "val" argument of 32 to clear a block of
+ * characters.
+ *
+ * @param sx source X
+ * @param sy source Y
+ * @param w width
+ * @param h height
+ * @param val value to set.
+ */
+ public void blockSet(int sx, int sy, int w, int h, int val, int style) {
+ if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
+ Log.e(TAG, "illegal arguments! " + sx + " " + sy + " " + w + " " + h + " " + val + " " + mColumns + " " + mScreenRows);
+ throw new IllegalArgumentException();
+ }
+
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ setChar(sx + x, sy + y, val, style);
+ }
+ }
+ }
+
+ /**
+ * Minimum API version for which we're willing to let Android try
+ * rendering conjoining Hangul jamo as composed syllable blocks.
+ *
+ * This appears to work on Android 4.1.2, 4.3, and 4.4 (real devices only;
+ * the emulator's broken for some reason), but not on 4.0.4 -- hence the
+ * choice of API 16 as the minimum.
+ */
+ static final int HANGUL_CONJOINING_MIN_SDK = 16;
+
+ /**
+ * Gives the display width of the code point in a monospace font.
+ *
+ * Nonspacing combining marks, format characters, and control characters
+ * have display width zero. East Asian fullwidth and wide characters
+ * have display width two. All other characters have display width one.
+ *
+ * Known issues:
+ * - Proper support for East Asian wide characters requires API >= 8.
+ * - Assigning all East Asian "ambiguous" characters a width of 1 may not
+ * be correct if Android renders those characters as wide in East Asian
+ * context (as the Unicode standard permits).
+ * - Isolated Hangul conjoining medial vowels and final consonants are
+ * treated as combining characters (they should only be combining when
+ * part of a Korean syllable block).
+ *
+ * @param codePoint A Unicode code point.
+ * @return The display width of the Unicode code point.
+ */
+ public static int charWidth(int codePoint) {
+ // Early out for ASCII printable characters
+ if (codePoint > 31 && codePoint < 127) {
+ return 1;
+ }
+
+ /* HACK: We're using ASCII ESC to save the location of the cursor
+ across screen resizes, so we need to pretend that it has width 1 */
+ if (codePoint == 27) {
+ return 1;
+ }
+
+ switch (Character.getType(codePoint)) {
+ case Character.CONTROL:
+ case Character.FORMAT:
+ case Character.NON_SPACING_MARK:
+ case Character.ENCLOSING_MARK:
+ return 0;
+ }
+
+ if ((codePoint >= 0x1160 && codePoint <= 0x11FF) ||
+ (codePoint >= 0xD7B0 && codePoint <= 0xD7FF)) {
+ if (AndroidCompat.SDK >= HANGUL_CONJOINING_MIN_SDK) {
+ /* Treat Hangul jamo medial vowels and final consonants as
+ * combining characters with width 0 to make jamo composition
+ * work correctly.
+ *
+ * XXX: This is wrong for medials/finals outside a Korean
+ * syllable block, but there's no easy solution to that
+ * problem, and we may as well at least get the common case
+ * right. */
+ return 0;
+ } else {
+ /* Older versions of Android didn't compose Hangul jamo, but
+ * instead rendered them as individual East Asian wide
+ * characters (despite Unicode defining medial vowels and final
+ * consonants as East Asian neutral/narrow). Treat them as
+ * width 2 characters to match the rendering. */
+ return 2;
+ }
+ }
+ if (Character.charCount(codePoint) == 1) {
+ // Android's getEastAsianWidth() only works for BMP characters
+ switch (AndroidCharacterCompat.getEastAsianWidth((char) codePoint)) {
+ case AndroidCharacterCompat.EAST_ASIAN_WIDTH_FULL_WIDTH:
+ case AndroidCharacterCompat.EAST_ASIAN_WIDTH_WIDE:
+ return 2;
+ }
+ } else {
+ // Outside the BMP, only the ideographic planes contain wide chars
+ switch ((codePoint >> 16) & 0xf) {
+ case 2: // Supplementary Ideographic Plane
+ case 3: // Tertiary Ideographic Plane
+ return 2;
+ }
+ }
+
+ return 1;
+ }
+
+ public static int charWidth(char cHigh, char cLow) {
+ return charWidth(Character.toCodePoint(cHigh, cLow));
+ }
+
+ /**
+ * Gives the display width of a code point in a char array
+ * in a monospace font.
+ *
+ * @param chars The array containing the code point in question.
+ * @param index The index into the array at which the code point starts.
+ * @return The display width of the Unicode code point.
+ */
+ public static int charWidth(char[] chars, int index) {
+ char c = chars[index];
+ if (Character.isHighSurrogate(c)) {
+ return charWidth(c, chars[index+1]);
+ } else {
+ return charWidth(c);
+ }
+ }
+
+ /**
+ * Get the contents of a line (or part of a line) of the transcript.
+ *
+ * The char[] array returned may be part of the internal representation
+ * of the line -- make a copy first if you want to modify it. The returned
+ * array may be longer than the requested portion of the transcript; in
+ * this case, the last character requested will be followed by a NUL, and
+ * the contents of the rest of the array could potentially be garbage.
+ *
+ * @param row The row number to get (-mActiveTranscriptRows..mScreenRows-1)
+ * @param x1 The first screen position that's wanted
+ * @param x2 One after the last screen position that's wanted
+ * @return A char[] array containing the requested contents
+ */
+ public char[] getLine(int row, int x1, int x2) {
+ return getLine(row, x1, x2, false);
+ }
+
+ /**
+ * Get the whole contents of a line of the transcript.
+ */
+ public char[] getLine(int row) {
+ return getLine(row, 0, mColumns, true);
+ }
+
+ private char[] getLine(int row, int x1, int x2, boolean strictBounds) {
+ if (row < -mActiveTranscriptRows || row > mScreenRows-1) {
+ throw new IllegalArgumentException();
+ }
+
+ int columns = mColumns;
+ row = externalToInternalRow(row);
+ if (mLines[row] == null) {
+ // Line is blank
+ return null;
+ }
+ if (mLines[row] instanceof char[]) {
+ // Line contains only regular-width BMP characters
+ if (x1 == 0 && x2 == columns) {
+ // Want the whole row? Easy.
+ return (char[]) mLines[row];
+ } else {
+ if (tmpLine == null || tmpLine.length < columns + 1) {
+ tmpLine = new char[columns+1];
+ }
+ int length = x2 - x1;
+ System.arraycopy(mLines[row], x1, tmpLine, 0, length);
+ tmpLine[length] = 0;
+ return tmpLine;
+ }
+ }
+
+ // Figure out how long the array needs to be
+ FullUnicodeLine line = (FullUnicodeLine) mLines[row];
+ char[] rawLine = line.getLine();
+
+ if (x1 == 0 && x2 == columns) {
+ /* We can return the raw line after ensuring it's NUL-terminated at
+ * the appropriate place */
+ int spaceUsed = line.getSpaceUsed();
+ if (spaceUsed < rawLine.length) {
+ rawLine[spaceUsed] = 0;
+ }
+ return rawLine;
+ }
+
+ x1 = line.findStartOfColumn(x1);
+ if (x2 < columns) {
+ int endCol = x2;
+ x2 = line.findStartOfColumn(endCol);
+ if (!strictBounds && endCol > 0 && endCol < columns - 1) {
+ /* If the end column is the middle of an East Asian wide
+ * character, include that character in the bounds */
+ if (x2 == line.findStartOfColumn(endCol - 1)) {
+ x2 = line.findStartOfColumn(endCol + 1);
+ }
+ }
+ } else {
+ x2 = line.getSpaceUsed();
+ }
+ int length = x2 - x1;
+
+ if (tmpLine == null || tmpLine.length < length + 1) {
+ tmpLine = new char[length+1];
+ }
+ System.arraycopy(rawLine, x1, tmpLine, 0, length);
+ tmpLine[length] = 0;
+ return tmpLine;
+ }
+
+ /**
+ * Get color/formatting information for a particular line.
+ * The returned object may be a pointer to a temporary buffer, only good
+ * until the next call to getLineColor.
+ */
+ public StyleRow getLineColor(int row, int x1, int x2) {
+ return getLineColor(row, x1, x2, false);
+ }
+
+ public StyleRow getLineColor(int row) {
+ return getLineColor(row, 0, mColumns, true);
+ }
+
+ private StyleRow getLineColor(int row, int x1, int x2, boolean strictBounds) {
+ if (row < -mActiveTranscriptRows || row > mScreenRows-1) {
+ throw new IllegalArgumentException();
+ }
+
+ row = externalToInternalRow(row);
+ StyleRow color = mColor[row];
+ StyleRow tmp = tmpColor;
+ if (color != null) {
+ int columns = mColumns;
+ if (!strictBounds && mLines[row] != null &&
+ mLines[row] instanceof FullUnicodeLine) {
+ FullUnicodeLine line = (FullUnicodeLine) mLines[row];
+ /* If either the start or the end column is in the middle of
+ * an East Asian wide character, include the appropriate column
+ * of style information */
+ if (x1 > 0 && line.findStartOfColumn(x1-1) == line.findStartOfColumn(x1)) {
+ --x1;
+ }
+ if (x2 < columns - 1 && line.findStartOfColumn(x2+1) == line.findStartOfColumn(x2)) {
+ ++x2;
+ }
+ }
+ if (x1 == 0 && x2 == columns) {
+ return color;
+ }
+ color.copy(x1, tmp, 0, x2-x1);
+ return tmp;
+ } else {
+ return null;
+ }
+ }
+
+ boolean isBasicLine(int row) {
+ if (row < -mActiveTranscriptRows || row > mScreenRows-1) {
+ throw new IllegalArgumentException();
+ }
+
+ return (mLines[externalToInternalRow(row)] instanceof char[]);
+ }
+
+ public boolean getChar(int row, int column) {
+ return getChar(row, column, 0);
+ }
+
+ public boolean getChar(int row, int column, int charIndex) {
+ return getChar(row, column, charIndex, new char[1], 0);
+ }
+
+ /**
+ * Get a character at a specific position in the transcript.
+ *
+ * @param row The row of the character to get.
+ * @param column The column of the character to get.
+ * @param charIndex The index of the character in the column to get
+ * (0 for the first character, 1 for the next, etc.)
+ * @param out The char[] array into which the character will be placed.
+ * @param offset The offset in the array at which the character will be placed.
+ * @return Whether or not there are characters following this one in the column.
+ */
+ public boolean getChar(int row, int column, int charIndex, char[] out, int offset) {
+ if (row < -mActiveTranscriptRows || row > mScreenRows-1) {
+ throw new IllegalArgumentException();
+ }
+ row = externalToInternalRow(row);
+
+ if (mLines[row] instanceof char[]) {
+ // Fast path: all regular-width BMP chars in the row
+ char[] line = (char[]) mLines[row];
+ out[offset] = line[column];
+ return false;
+ }
+
+ FullUnicodeLine line = (FullUnicodeLine) mLines[row];
+ return line.getChar(column, charIndex, out, offset);
+ }
+
+ private boolean isBasicChar(int codePoint) {
+ return !(charWidth(codePoint) != 1 || Character.charCount(codePoint) != 1);
+ }
+
+ private char[] allocateBasicLine(int row, int columns) {
+ char[] line = new char[columns];
+
+ // Fill the line with blanks
+ for (int i = 0; i < columns; ++i) {
+ line[i] = ' ';
+ }
+
+ mLines[row] = line;
+ if (mColor[row] == null) {
+ mColor[row] = new StyleRow(0, columns);
+ }
+ return line;
+ }
+
+ private FullUnicodeLine allocateFullLine(int row, int columns) {
+ FullUnicodeLine line = new FullUnicodeLine(columns);
+
+ mLines[row] = line;
+ if (mColor[row] == null) {
+ mColor[row] = new StyleRow(0, columns);
+ }
+ return line;
+ }
+
+ public boolean setChar(int column, int row, int codePoint, int style) {
+ if (!setChar(column, row, codePoint)) {
+ return false;
+ }
+
+ row = externalToInternalRow(row);
+ mColor[row].set(column, style);
+
+ return true;
+ }
+
+ public boolean setChar(int column, int row, int codePoint) {
+ if (row >= mScreenRows || column >= mColumns) {
+ Log.e(TAG, "illegal arguments! " + row + " " + column + " " + mScreenRows + " " + mColumns);
+ throw new IllegalArgumentException();
+ }
+ row = externalToInternalRow(row);
+
+ /*
+ * Whether data contains non-BMP or characters with charWidth != 1
+ * 0 - false; 1 - true; -1 - undetermined
+ */
+ int basicMode = -1;
+
+ // Allocate a row on demand
+ if (mLines[row] == null) {
+ if (isBasicChar(codePoint)) {
+ allocateBasicLine(row, mColumns);
+ basicMode = 1;
+ } else {
+ allocateFullLine(row, mColumns);
+ basicMode = 0;
+ }
+ }
+
+ if (mLines[row] instanceof char[]) {
+ char[] line = (char[]) mLines[row];
+
+ if (basicMode == -1) {
+ if (isBasicChar(codePoint)) {
+ basicMode = 1;
+ } else {
+ basicMode = 0;
+ }
+ }
+
+ if (basicMode == 1) {
+ // Fast path -- just put the char in the array
+ line[column] = (char) codePoint;
+ return true;
+ }
+
+ // Need to switch to the full-featured mode
+ mLines[row] = new FullUnicodeLine(line);
+ }
+
+ FullUnicodeLine line = (FullUnicodeLine) mLines[row];
+ line.setChar(column, codePoint);
+ return true;
+ }
+}
+
+/*
+ * A representation of a line that's capable of handling non-BMP characters,
+ * East Asian wide characters, and combining characters.
+ *
+ * The text of the line is stored in an array of char[], allowing easy
+ * conversion to a String and/or reuse by other string-handling functions.
+ * An array of short[] is used to keep track of the difference between a column
+ * and the starting index corresponding to its contents in the char[] array (so
+ * if column 42 starts at index 45 in the char[] array, the offset stored is 3).
+ * Column 0 always starts at index 0 in the char[] array, so we use that
+ * element of the array to keep track of how much of the char[] array we're
+ * using at the moment.
+ */
+class FullUnicodeLine {
+ private static final float SPARE_CAPACITY_FACTOR = 1.5f;
+
+ private char[] mText;
+ private short[] mOffset;
+ private int mColumns;
+
+ public FullUnicodeLine(int columns) {
+ commonConstructor(columns);
+ char[] text = mText;
+ // Fill in the line with blanks
+ for (int i = 0; i < columns; ++i) {
+ text[i] = ' ';
+ }
+ // Store the space used
+ mOffset[0] = (short) columns;
+ }
+
+ public FullUnicodeLine(char[] basicLine) {
+ commonConstructor(basicLine.length);
+ System.arraycopy(basicLine, 0, mText, 0, mColumns);
+ // Store the space used
+ mOffset[0] = (short) basicLine.length;
+ }
+
+ private void commonConstructor(int columns) {
+ mColumns = columns;
+ mOffset = new short[columns];
+ mText = new char[(int)(SPARE_CAPACITY_FACTOR*columns)];
+ }
+
+ public int getSpaceUsed() {
+ return mOffset[0];
+ }
+
+ public char[] getLine() {
+ return mText;
+ }
+
+ public int findStartOfColumn(int column) {
+ if (column == 0) {
+ return 0;
+ } else {
+ return column + mOffset[column];
+ }
+ }
+
+ public boolean getChar(int column, int charIndex, char[] out, int offset) {
+ int pos = findStartOfColumn(column);
+ int length;
+ if (column + 1 < mColumns) {
+ length = findStartOfColumn(column + 1) - pos;
+ } else {
+ length = getSpaceUsed() - pos;
+ }
+ if (charIndex >= length) {
+ throw new IllegalArgumentException();
+ }
+ out[offset] = mText[pos + charIndex];
+ return (charIndex + 1 < length);
+ }
+
+ public void setChar(int column, int codePoint) {
+ int columns = mColumns;
+ if (column < 0 || column >= columns) {
+ throw new IllegalArgumentException();
+ }
+
+ char[] text = mText;
+ short[] offset = mOffset;
+ int spaceUsed = offset[0];
+
+ int pos = findStartOfColumn(column);
+
+ int charWidth = UnicodeTranscript.charWidth(codePoint);
+ int oldCharWidth = UnicodeTranscript.charWidth(text, pos);
+
+ if (charWidth == 2 && column == columns - 1) {
+ // A width 2 character doesn't fit in the last column.
+ codePoint = ' ';
+ charWidth = 1;
+ }
+
+ boolean wasExtraColForWideChar = false;
+ if (oldCharWidth == 2 && column > 0) {
+ /* If the previous screen column starts at the same offset in the
+ * array as this one, this column must be the second column used
+ * by an East Asian wide character */
+ wasExtraColForWideChar = (findStartOfColumn(column - 1) == pos);
+ }
+
+ // Get the number of elements in the mText array this column uses now
+ int oldLen;
+ if (wasExtraColForWideChar && column + 1 < columns) {
+ oldLen = findStartOfColumn(column + 1) - pos;
+ } else if (column + oldCharWidth < columns) {
+ oldLen = findStartOfColumn(column+oldCharWidth) - pos;
+ } else {
+ oldLen = spaceUsed - pos;
+ }
+
+ // Find how much space this column will need
+ int newLen = Character.charCount(codePoint);
+ if (charWidth == 0) {
+ /* Combining characters are added to the contents of the column
+ instead of overwriting them, so that they modify the existing
+ contents */
+ newLen += oldLen;
+ }
+ int shift = newLen - oldLen;
+
+ // Shift the rest of the line right to make room if necessary
+ if (shift > 0) {
+ if (spaceUsed + shift > text.length) {
+ // We need to grow the array
+ char[] newText = new char[text.length + columns];
+ System.arraycopy(text, 0, newText, 0, pos);
+ System.arraycopy(text, pos + oldLen, newText, pos + newLen, spaceUsed - pos - oldLen);
+ mText = text = newText;
+ } else {
+ System.arraycopy(text, pos + oldLen, text, pos + newLen, spaceUsed - pos - oldLen);
+ }
+ }
+
+ // Store the character
+ if (charWidth > 0) {
+ Character.toChars(codePoint, text, pos);
+ } else {
+ /* Store a combining character at the end of the existing contents,
+ so that it modifies them */
+ Character.toChars(codePoint, text, pos + oldLen);
+ }
+
+ // Shift the rest of the line left to eliminate gaps if necessary
+ if (shift < 0) {
+ System.arraycopy(text, pos + oldLen, text, pos + newLen, spaceUsed - pos - oldLen);
+ }
+
+ // Update space used
+ if (shift != 0) {
+ spaceUsed += shift;
+ offset[0] = (short) spaceUsed;
+ }
+
+ /*
+ * Handle cases where we need to pad with spaces to preserve column
+ * alignment
+ *
+ * width 2 -> width 1: pad with a space before or after the new
+ * character, depending on which of the two previously-occupied columns
+ * we wrote into
+ *
+ * inserting width 2 character into the second column of an existing
+ * width 2 character: pad with a space before the new character
+ */
+ if (oldCharWidth == 2 && charWidth == 1 || wasExtraColForWideChar && charWidth == 2) {
+ int nextPos = pos + newLen;
+ char[] newText = text;
+ if (spaceUsed + 1 > text.length) {
+ // Array needs growing
+ newText = new char[text.length + columns];
+ System.arraycopy(text, 0, newText, 0, wasExtraColForWideChar ? pos : nextPos);
+ }
+
+ if (wasExtraColForWideChar) {
+ // Padding goes before the new character
+ System.arraycopy(text, pos, newText, pos + 1, spaceUsed - pos);
+ newText[pos] = ' ';
+ } else {
+ // Padding goes after the new character
+ System.arraycopy(text, nextPos, newText, nextPos + 1, spaceUsed - nextPos);
+ newText[nextPos] = ' ';
+ }
+
+ if (newText != text) {
+ // Update mText to point to the newly grown array
+ mText = text = newText;
+ }
+
+ // Update space used
+ spaceUsed = ++offset[0];
+
+ // Correct the offset for the just-modified column to reflect
+ // width change
+ if (wasExtraColForWideChar) {
+ ++offset[column];
+ ++pos;
+ } else {
+ if (column == 0) {
+ offset[1] = (short) (newLen - 1);
+ } else if (column + 1 < columns) {
+ offset[column + 1] = (short) (offset[column] + newLen - 1);
+ }
+ ++column;
+ }
+
+ ++shift;
+ }
+
+ /*
+ * Handle cases where we need to clobber the contents of the next
+ * column in order to preserve column alignment
+ *
+ * width 1 -> width 2: should clobber the contents of the next
+ * column (if next column contains wide char, need to pad with a space)
+ *
+ * inserting width 2 character into the second column of an existing
+ * width 2 character: same
+ */
+ if (oldCharWidth == 1 && charWidth == 2 || wasExtraColForWideChar && charWidth == 2) {
+ if (column == columns - 2) {
+ // Correct offset for the next column to reflect width change
+ offset[column + 1] = (short) (offset[column] - 1);
+
+ // Truncate the line after this character.
+ offset[0] = (short) (pos + newLen);
+ shift = 0;
+ } else {
+ // Overwrite the contents of the next column.
+ int nextPos = pos + newLen;
+ int nextWidth = UnicodeTranscript.charWidth(text, nextPos);
+ int nextLen;
+ if (column + nextWidth + 1 < columns) {
+ nextLen = findStartOfColumn(column + nextWidth + 1) + shift - nextPos;
+ } else {
+ nextLen = spaceUsed - nextPos;
+ }
+
+ if (nextWidth == 2) {
+ text[nextPos] = ' ';
+ // Shift the array to match
+ if (nextLen > 1) {
+ System.arraycopy(text, nextPos + nextLen, text, nextPos + 1, spaceUsed - nextPos - nextLen);
+ shift -= nextLen - 1;
+ offset[0] -= nextLen - 1;
+ }
+ } else {
+ // Shift the array leftwards
+ System.arraycopy(text, nextPos + nextLen, text, nextPos, spaceUsed - nextPos - nextLen);
+ shift -= nextLen;
+
+ // Truncate the line
+ offset[0] -= nextLen;
+ }
+
+ // Correct the offset for the next column to reflect width change
+ if (column == 0) {
+ offset[1] = -1;
+ } else {
+ offset[column + 1] = (short) (offset[column] - 1);
+ }
+ ++column;
+ }
+ }
+
+ // Update offset table
+ if (shift != 0) {
+ for (int i = column + 1; i < columns; ++i) {
+ offset[i] += shift;
+ }
+ }
+ }
+}
+
diff --git a/src/jackpal/androidterm/TermPreferences.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/UpdateCallback.java
similarity index 61%
rename from src/jackpal/androidterm/TermPreferences.java
rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/UpdateCallback.java
index 1bbf0e7ce..b2dbe2184 100644
--- a/src/jackpal/androidterm/TermPreferences.java
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/UpdateCallback.java
@@ -14,19 +14,14 @@
* limitations under the License.
*/
-package jackpal.androidterm2;
-
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-
-public class TermPreferences extends PreferenceActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Load the preferences from an XML resource
- addPreferencesFromResource(R.xml.preferences);
- }
+package jackpal.androidterm.emulatorview;
+/**
+ * Generic callback to be invoked to notify of updates.
+ */
+public interface UpdateCallback {
+ /**
+ * Callback function to be invoked when an update happens.
+ */
+ void onUpdate();
}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCharacterCompat.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCharacterCompat.java
new file mode 100644
index 000000000..91762a6a7
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCharacterCompat.java
@@ -0,0 +1,29 @@
+package jackpal.androidterm.emulatorview.compat;
+
+import android.text.AndroidCharacter;
+
+/**
+ * Definitions related to android.text.AndroidCharacter
+ */
+public class AndroidCharacterCompat {
+ public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0;
+ public static final int EAST_ASIAN_WIDTH_AMBIGUOUS = 1;
+ public static final int EAST_ASIAN_WIDTH_HALF_WIDTH = 2;
+ public static final int EAST_ASIAN_WIDTH_FULL_WIDTH = 3;
+ public static final int EAST_ASIAN_WIDTH_NARROW = 4;
+ public static final int EAST_ASIAN_WIDTH_WIDE = 5;
+
+ private static class Api8OrLater {
+ public static int getEastAsianWidth(char c) {
+ return AndroidCharacter.getEastAsianWidth(c);
+ }
+ }
+
+ public static int getEastAsianWidth(char c) {
+ if (AndroidCompat.SDK >= 8) {
+ return Api8OrLater.getEastAsianWidth(c);
+ } else {
+ return EAST_ASIAN_WIDTH_NARROW;
+ }
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCompat.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCompat.java
new file mode 100644
index 000000000..9f0555869
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCompat.java
@@ -0,0 +1,40 @@
+package jackpal.androidterm.emulatorview.compat;
+
+/**
+ * The classes in this package take advantage of the fact that the VM does
+ * not attempt to load a class until it's accessed, and the verifier
+ * does not run until a class is loaded. By keeping the methods which
+ * are unavailable on older platforms in subclasses which are only ever
+ * accessed on platforms where they are available, we can preserve
+ * compatibility with older platforms without resorting to reflection.
+ *
+ * See http://developer.android.com/resources/articles/backward-compatibility.html
+ * and http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html
+ * for further discussion of this technique.
+ */
+
+public class AndroidCompat {
+ public final static int SDK = getSDK();
+
+ private final static int getSDK() {
+ int result;
+ try {
+ result = AndroidLevel4PlusCompat.getSDKInt();
+ } catch (VerifyError e) {
+ // We must be at an SDK level less than 4.
+ try {
+ result = Integer.valueOf(android.os.Build.VERSION.SDK);
+ } catch (NumberFormatException e2) {
+ // Couldn't parse string, assume the worst.
+ result = 1;
+ }
+ }
+ return result;
+ }
+}
+
+class AndroidLevel4PlusCompat {
+ static int getSDKInt() {
+ return android.os.Build.VERSION.SDK_INT;
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompat.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompat.java
new file mode 100644
index 000000000..d38cfd032
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompat.java
@@ -0,0 +1,9 @@
+package jackpal.androidterm.emulatorview.compat;
+
+public interface ClipboardManagerCompat {
+ CharSequence getText();
+
+ boolean hasText();
+
+ void setText(CharSequence text);
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatFactory.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatFactory.java
new file mode 100644
index 000000000..5eaf2a215
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatFactory.java
@@ -0,0 +1,18 @@
+package jackpal.androidterm.emulatorview.compat;
+
+import android.content.Context;
+
+public class ClipboardManagerCompatFactory {
+
+ private ClipboardManagerCompatFactory() {
+ /* singleton */
+ }
+
+ public static ClipboardManagerCompat getManager(Context context) {
+ if (AndroidCompat.SDK < 11) {
+ return new ClipboardManagerCompatV1(context);
+ } else {
+ return new ClipboardManagerCompatV11(context);
+ }
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV1.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV1.java
new file mode 100644
index 000000000..672e5c2a8
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV1.java
@@ -0,0 +1,29 @@
+package jackpal.androidterm.emulatorview.compat;
+
+import android.content.Context;
+import android.text.ClipboardManager;
+
+@SuppressWarnings("deprecation")
+public class ClipboardManagerCompatV1 implements ClipboardManagerCompat {
+ private final ClipboardManager clip;
+
+ public ClipboardManagerCompatV1(Context context) {
+ clip = (ClipboardManager) context.getApplicationContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ }
+
+ @Override
+ public CharSequence getText() {
+ return clip.getText();
+ }
+
+ @Override
+ public boolean hasText() {
+ return clip.hasText();
+ }
+
+ @Override
+ public void setText(CharSequence text) {
+ clip.setText(text);
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV11.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV11.java
new file mode 100644
index 000000000..dfdbd7868
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV11.java
@@ -0,0 +1,35 @@
+package jackpal.androidterm.emulatorview.compat;
+
+import android.annotation.SuppressLint;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.ClipboardManager;
+
+@SuppressLint("NewApi")
+public class ClipboardManagerCompatV11 implements ClipboardManagerCompat {
+ private final ClipboardManager clip;
+
+ public ClipboardManagerCompatV11(Context context) {
+ clip = (ClipboardManager) context.getApplicationContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ }
+
+ @Override
+ public CharSequence getText() {
+ ClipData.Item item = clip.getPrimaryClip().getItemAt(0);
+ return item.getText();
+ }
+
+ @Override
+ public boolean hasText() {
+ return (clip.hasPrimaryClip() && clip.getPrimaryClipDescription()
+ .hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN));
+ }
+
+ @Override
+ public void setText(CharSequence text) {
+ ClipData clipData = ClipData.newPlainText("simple text", text);
+ clip.setPrimaryClip(clipData);
+ }
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeyCharacterMapCompat.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeyCharacterMapCompat.java
new file mode 100644
index 000000000..242f08868
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeyCharacterMapCompat.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 Jack Palevich
+ *
+ * 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 jackpal.androidterm.emulatorview.compat;
+
+import android.view.KeyCharacterMap;
+
+public abstract class KeyCharacterMapCompat {
+ public static final int MODIFIER_BEHAVIOR_CHORDED = 0;
+ public static final int MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED = 1;
+
+ public static KeyCharacterMapCompat wrap(Object map) {
+ if (map != null) {
+ if (AndroidCompat.SDK >= 11) {
+ return new KeyCharacterMapApi11OrLater(map);
+ }
+ }
+ return null;
+ }
+
+ private static class KeyCharacterMapApi11OrLater
+ extends KeyCharacterMapCompat {
+ private KeyCharacterMap mMap;
+ public KeyCharacterMapApi11OrLater(Object map) {
+ mMap = (KeyCharacterMap) map;
+ }
+ public int getModifierBehaviour() {
+ return mMap.getModifierBehavior();
+ }
+ }
+
+ public abstract int getModifierBehaviour();
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeycodeConstants.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeycodeConstants.java
new file mode 100644
index 000000000..d025561b2
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeycodeConstants.java
@@ -0,0 +1,490 @@
+package jackpal.androidterm.emulatorview.compat;
+
+/**
+ * Keycode constants and modifier masks for use with keyboard event listeners.
+ *
+ * The Meta masks (ctrl, alt, shift, and meta) are used as follows:
+ * KeyEvent keyEvent = ...;
+ * boolean isCtrlPressed = (keyEvent.getMetaState() & META_CTRL_ON) != 0
+ *
+ * Contains the complete set of Android key codes that were defined as of the 2.3 API.
+ * We could pull in the constants from the 2.3 API, but then we would need to raise the
+ * SDK minVersion in the manifest. We want to keep compatibility with Android 1.6,
+ * and raising this level could result in the accidental use of a newer API.
+ */
+public class KeycodeConstants {
+
+ /** Key code constant: Unknown key code. */
+ public static final int KEYCODE_UNKNOWN = 0;
+ /** Key code constant: Soft Left key.
+ * Usually situated below the display on phones and used as a multi-function
+ * feature key for selecting a software defined function shown on the bottom left
+ * of the display. */
+ public static final int KEYCODE_SOFT_LEFT = 1;
+ /** Key code constant: Soft Right key.
+ * Usually situated below the display on phones and used as a multi-function
+ * feature key for selecting a software defined function shown on the bottom right
+ * of the display. */
+ public static final int KEYCODE_SOFT_RIGHT = 2;
+ /** Key code constant: Home key.
+ * This key is handled by the framework and is never delivered to applications. */
+ public static final int KEYCODE_HOME = 3;
+ /** Key code constant: Back key. */
+ public static final int KEYCODE_BACK = 4;
+ /** Key code constant: Call key. */
+ public static final int KEYCODE_CALL = 5;
+ /** Key code constant: End Call key. */
+ public static final int KEYCODE_ENDCALL = 6;
+ /** Key code constant: '0' key. */
+ public static final int KEYCODE_0 = 7;
+ /** Key code constant: '1' key. */
+ public static final int KEYCODE_1 = 8;
+ /** Key code constant: '2' key. */
+ public static final int KEYCODE_2 = 9;
+ /** Key code constant: '3' key. */
+ public static final int KEYCODE_3 = 10;
+ /** Key code constant: '4' key. */
+ public static final int KEYCODE_4 = 11;
+ /** Key code constant: '5' key. */
+ public static final int KEYCODE_5 = 12;
+ /** Key code constant: '6' key. */
+ public static final int KEYCODE_6 = 13;
+ /** Key code constant: '7' key. */
+ public static final int KEYCODE_7 = 14;
+ /** Key code constant: '8' key. */
+ public static final int KEYCODE_8 = 15;
+ /** Key code constant: '9' key. */
+ public static final int KEYCODE_9 = 16;
+ /** Key code constant: '*' key. */
+ public static final int KEYCODE_STAR = 17;
+ /** Key code constant: '#' key. */
+ public static final int KEYCODE_POUND = 18;
+ /** Key code constant: Directional Pad Up key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_UP = 19;
+ /** Key code constant: Directional Pad Down key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_DOWN = 20;
+ /** Key code constant: Directional Pad Left key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_LEFT = 21;
+ /** Key code constant: Directional Pad Right key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_RIGHT = 22;
+ /** Key code constant: Directional Pad Center key.
+ * May also be synthesized from trackball motions. */
+ public static final int KEYCODE_DPAD_CENTER = 23;
+ /** Key code constant: Volume Up key.
+ * Adjusts the speaker volume up. */
+ public static final int KEYCODE_VOLUME_UP = 24;
+ /** Key code constant: Volume Down key.
+ * Adjusts the speaker volume down. */
+ public static final int KEYCODE_VOLUME_DOWN = 25;
+ /** Key code constant: Power key. */
+ public static final int KEYCODE_POWER = 26;
+ /** Key code constant: Camera key.
+ * Used to launch a camera application or take pictures. */
+ public static final int KEYCODE_CAMERA = 27;
+ /** Key code constant: Clear key. */
+ public static final int KEYCODE_CLEAR = 28;
+ /** Key code constant: 'A' key. */
+ public static final int KEYCODE_A = 29;
+ /** Key code constant: 'B' key. */
+ public static final int KEYCODE_B = 30;
+ /** Key code constant: 'C' key. */
+ public static final int KEYCODE_C = 31;
+ /** Key code constant: 'D' key. */
+ public static final int KEYCODE_D = 32;
+ /** Key code constant: 'E' key. */
+ public static final int KEYCODE_E = 33;
+ /** Key code constant: 'F' key. */
+ public static final int KEYCODE_F = 34;
+ /** Key code constant: 'G' key. */
+ public static final int KEYCODE_G = 35;
+ /** Key code constant: 'H' key. */
+ public static final int KEYCODE_H = 36;
+ /** Key code constant: 'I' key. */
+ public static final int KEYCODE_I = 37;
+ /** Key code constant: 'J' key. */
+ public static final int KEYCODE_J = 38;
+ /** Key code constant: 'K' key. */
+ public static final int KEYCODE_K = 39;
+ /** Key code constant: 'L' key. */
+ public static final int KEYCODE_L = 40;
+ /** Key code constant: 'M' key. */
+ public static final int KEYCODE_M = 41;
+ /** Key code constant: 'N' key. */
+ public static final int KEYCODE_N = 42;
+ /** Key code constant: 'O' key. */
+ public static final int KEYCODE_O = 43;
+ /** Key code constant: 'P' key. */
+ public static final int KEYCODE_P = 44;
+ /** Key code constant: 'Q' key. */
+ public static final int KEYCODE_Q = 45;
+ /** Key code constant: 'R' key. */
+ public static final int KEYCODE_R = 46;
+ /** Key code constant: 'S' key. */
+ public static final int KEYCODE_S = 47;
+ /** Key code constant: 'T' key. */
+ public static final int KEYCODE_T = 48;
+ /** Key code constant: 'U' key. */
+ public static final int KEYCODE_U = 49;
+ /** Key code constant: 'V' key. */
+ public static final int KEYCODE_V = 50;
+ /** Key code constant: 'W' key. */
+ public static final int KEYCODE_W = 51;
+ /** Key code constant: 'X' key. */
+ public static final int KEYCODE_X = 52;
+ /** Key code constant: 'Y' key. */
+ public static final int KEYCODE_Y = 53;
+ /** Key code constant: 'Z' key. */
+ public static final int KEYCODE_Z = 54;
+ /** Key code constant: ',' key. */
+ public static final int KEYCODE_COMMA = 55;
+ /** Key code constant: '.' key. */
+ public static final int KEYCODE_PERIOD = 56;
+ /** Key code constant: Left Alt modifier key. */
+ public static final int KEYCODE_ALT_LEFT = 57;
+ /** Key code constant: Right Alt modifier key. */
+ public static final int KEYCODE_ALT_RIGHT = 58;
+ /** Key code constant: Left Shift modifier key. */
+ public static final int KEYCODE_SHIFT_LEFT = 59;
+ /** Key code constant: Right Shift modifier key. */
+ public static final int KEYCODE_SHIFT_RIGHT = 60;
+ /** Key code constant: Tab key. */
+ public static final int KEYCODE_TAB = 61;
+ /** Key code constant: Space key. */
+ public static final int KEYCODE_SPACE = 62;
+ /** Key code constant: Symbol modifier key.
+ * Used to enter alternate symbols. */
+ public static final int KEYCODE_SYM = 63;
+ /** Key code constant: Explorer special function key.
+ * Used to launch a browser application. */
+ public static final int KEYCODE_EXPLORER = 64;
+ /** Key code constant: Envelope special function key.
+ * Used to launch a mail application. */
+ public static final int KEYCODE_ENVELOPE = 65;
+ /** Key code constant: Enter key. */
+ public static final int KEYCODE_ENTER = 66;
+ /** Key code constant: Backspace key.
+ * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
+ public static final int KEYCODE_DEL = 67;
+ /** Key code constant: '`' (backtick) key. */
+ public static final int KEYCODE_GRAVE = 68;
+ /** Key code constant: '-'. */
+ public static final int KEYCODE_MINUS = 69;
+ /** Key code constant: '=' key. */
+ public static final int KEYCODE_EQUALS = 70;
+ /** Key code constant: '[' key. */
+ public static final int KEYCODE_LEFT_BRACKET = 71;
+ /** Key code constant: ']' key. */
+ public static final int KEYCODE_RIGHT_BRACKET = 72;
+ /** Key code constant: '\' key. */
+ public static final int KEYCODE_BACKSLASH = 73;
+ /** Key code constant: ';' key. */
+ public static final int KEYCODE_SEMICOLON = 74;
+ /** Key code constant: ''' (apostrophe) key. */
+ public static final int KEYCODE_APOSTROPHE = 75;
+ /** Key code constant: '/' key. */
+ public static final int KEYCODE_SLASH = 76;
+ /** Key code constant: '@' key. */
+ public static final int KEYCODE_AT = 77;
+ /** Key code constant: Number modifier key.
+ * Used to enter numeric symbols.
+ * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
+ * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
+ public static final int KEYCODE_NUM = 78;
+ /** Key code constant: Headset Hook key.
+ * Used to hang up calls and stop media. */
+ public static final int KEYCODE_HEADSETHOOK = 79;
+ /** Key code constant: Camera Focus key.
+ * Used to focus the camera. */
+ public static final int KEYCODE_FOCUS = 80; // *Camera* focus
+ /** Key code constant: '+' key. */
+ public static final int KEYCODE_PLUS = 81;
+ /** Key code constant: Menu key. */
+ public static final int KEYCODE_MENU = 82;
+ /** Key code constant: Notification key. */
+ public static final int KEYCODE_NOTIFICATION = 83;
+ /** Key code constant: Search key. */
+ public static final int KEYCODE_SEARCH = 84;
+ /** Key code constant: Play/Pause media key. */
+ public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
+ /** Key code constant: Stop media key. */
+ public static final int KEYCODE_MEDIA_STOP = 86;
+ /** Key code constant: Play Next media key. */
+ public static final int KEYCODE_MEDIA_NEXT = 87;
+ /** Key code constant: Play Previous media key. */
+ public static final int KEYCODE_MEDIA_PREVIOUS = 88;
+ /** Key code constant: Rewind media key. */
+ public static final int KEYCODE_MEDIA_REWIND = 89;
+ /** Key code constant: Fast Forward media key. */
+ public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
+ /** Key code constant: Mute key.
+ * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
+ public static final int KEYCODE_MUTE = 91;
+ /** Key code constant: Page Up key. */
+ public static final int KEYCODE_PAGE_UP = 92;
+ /** Key code constant: Page Down key. */
+ public static final int KEYCODE_PAGE_DOWN = 93;
+ /** Key code constant: Picture Symbols modifier key.
+ * Used to switch symbol sets (Emoji, Kao-moji). */
+ public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji)
+ /** Key code constant: Switch Charset modifier key.
+ * Used to switch character sets (Kanji, Katakana). */
+ public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
+ /** Key code constant: A Button key.
+ * On a game controller, the A button should be either the button labeled A
+ * or the first button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_A = 96;
+ /** Key code constant: B Button key.
+ * On a game controller, the B button should be either the button labeled B
+ * or the second button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_B = 97;
+ /** Key code constant: C Button key.
+ * On a game controller, the C button should be either the button labeled C
+ * or the third button on the upper row of controller buttons. */
+ public static final int KEYCODE_BUTTON_C = 98;
+ /** Key code constant: X Button key.
+ * On a game controller, the X button should be either the button labeled X
+ * or the first button on the lower row of controller buttons. */
+ public static final int KEYCODE_BUTTON_X = 99;
+ /** Key code constant: Y Button key.
+ * On a game controller, the Y button should be either the button labeled Y
+ * or the second button on the lower row of controller buttons. */
+ public static final int KEYCODE_BUTTON_Y = 100;
+ /** Key code constant: Z Button key.
+ * On a game controller, the Z button should be either the button labeled Z
+ * or the third button on the lower row of controller buttons. */
+ public static final int KEYCODE_BUTTON_Z = 101;
+ /** Key code constant: L1 Button key.
+ * On a game controller, the L1 button should be either the button labeled L1 (or L)
+ * or the top left trigger button. */
+ public static final int KEYCODE_BUTTON_L1 = 102;
+ /** Key code constant: R1 Button key.
+ * On a game controller, the R1 button should be either the button labeled R1 (or R)
+ * or the top right trigger button. */
+ public static final int KEYCODE_BUTTON_R1 = 103;
+ /** Key code constant: L2 Button key.
+ * On a game controller, the L2 button should be either the button labeled L2
+ * or the bottom left trigger button. */
+ public static final int KEYCODE_BUTTON_L2 = 104;
+ /** Key code constant: R2 Button key.
+ * On a game controller, the R2 button should be either the button labeled R2
+ * or the bottom right trigger button. */
+ public static final int KEYCODE_BUTTON_R2 = 105;
+ /** Key code constant: Left Thumb Button key.
+ * On a game controller, the left thumb button indicates that the left (or only)
+ * joystick is pressed. */
+ public static final int KEYCODE_BUTTON_THUMBL = 106;
+ /** Key code constant: Right Thumb Button key.
+ * On a game controller, the right thumb button indicates that the right
+ * joystick is pressed. */
+ public static final int KEYCODE_BUTTON_THUMBR = 107;
+ /** Key code constant: Start Button key.
+ * On a game controller, the button labeled Start. */
+ public static final int KEYCODE_BUTTON_START = 108;
+ /** Key code constant: Select Button key.
+ * On a game controller, the button labeled Select. */
+ public static final int KEYCODE_BUTTON_SELECT = 109;
+ /** Key code constant: Mode Button key.
+ * On a game controller, the button labeled Mode. */
+ public static final int KEYCODE_BUTTON_MODE = 110;
+ /** Key code constant: Escape key. */
+ public static final int KEYCODE_ESCAPE = 111;
+ /** Key code constant: Forward Delete key.
+ * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
+ public static final int KEYCODE_FORWARD_DEL = 112;
+ /** Key code constant: Left Control modifier key. */
+ public static final int KEYCODE_CTRL_LEFT = 113;
+ /** Key code constant: Right Control modifier key. */
+ public static final int KEYCODE_CTRL_RIGHT = 114;
+ /** Key code constant: Caps Lock modifier key. */
+ public static final int KEYCODE_CAPS_LOCK = 115;
+ /** Key code constant: Scroll Lock key. */
+ public static final int KEYCODE_SCROLL_LOCK = 116;
+ /** Key code constant: Left Meta modifier key. */
+ public static final int KEYCODE_META_LEFT = 117;
+ /** Key code constant: Right Meta modifier key. */
+ public static final int KEYCODE_META_RIGHT = 118;
+ /** Key code constant: Function modifier key. */
+ public static final int KEYCODE_FUNCTION = 119;
+ /** Key code constant: System Request / Print Screen key. */
+ public static final int KEYCODE_SYSRQ = 120;
+ /** Key code constant: Break / Pause key. */
+ public static final int KEYCODE_BREAK = 121;
+ /** Key code constant: Home Movement key.
+ * Used for scrolling or moving the cursor around to the start of a line
+ * or to the top of a list. */
+ public static final int KEYCODE_MOVE_HOME = 122;
+ /** Key code constant: End Movement key.
+ * Used for scrolling or moving the cursor around to the end of a line
+ * or to the bottom of a list. */
+ public static final int KEYCODE_MOVE_END = 123;
+ /** Key code constant: Insert key.
+ * Toggles insert / overwrite edit mode. */
+ public static final int KEYCODE_INSERT = 124;
+ /** Key code constant: Forward key.
+ * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */
+ public static final int KEYCODE_FORWARD = 125;
+ /** Key code constant: Play media key. */
+ public static final int KEYCODE_MEDIA_PLAY = 126;
+ /** Key code constant: Pause media key. */
+ public static final int KEYCODE_MEDIA_PAUSE = 127;
+ /** Key code constant: Close media key.
+ * May be used to close a CD tray, for example. */
+ public static final int KEYCODE_MEDIA_CLOSE = 128;
+ /** Key code constant: Eject media key.
+ * May be used to eject a CD tray, for example. */
+ public static final int KEYCODE_MEDIA_EJECT = 129;
+ /** Key code constant: Record media key. */
+ public static final int KEYCODE_MEDIA_RECORD = 130;
+ /** Key code constant: F1 key. */
+ public static final int KEYCODE_F1 = 131;
+ /** Key code constant: F2 key. */
+ public static final int KEYCODE_F2 = 132;
+ /** Key code constant: F3 key. */
+ public static final int KEYCODE_F3 = 133;
+ /** Key code constant: F4 key. */
+ public static final int KEYCODE_F4 = 134;
+ /** Key code constant: F5 key. */
+ public static final int KEYCODE_F5 = 135;
+ /** Key code constant: F6 key. */
+ public static final int KEYCODE_F6 = 136;
+ /** Key code constant: F7 key. */
+ public static final int KEYCODE_F7 = 137;
+ /** Key code constant: F8 key. */
+ public static final int KEYCODE_F8 = 138;
+ /** Key code constant: F9 key. */
+ public static final int KEYCODE_F9 = 139;
+ /** Key code constant: F10 key. */
+ public static final int KEYCODE_F10 = 140;
+ /** Key code constant: F11 key. */
+ public static final int KEYCODE_F11 = 141;
+ /** Key code constant: F12 key. */
+ public static final int KEYCODE_F12 = 142;
+ /** Key code constant: Num Lock modifier key.
+ * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
+ * This key generally modifies the behavior of other keys on the numeric keypad. */
+ public static final int KEYCODE_NUM_LOCK = 143;
+ /** Key code constant: Numeric keypad '0' key. */
+ public static final int KEYCODE_NUMPAD_0 = 144;
+ /** Key code constant: Numeric keypad '1' key. */
+ public static final int KEYCODE_NUMPAD_1 = 145;
+ /** Key code constant: Numeric keypad '2' key. */
+ public static final int KEYCODE_NUMPAD_2 = 146;
+ /** Key code constant: Numeric keypad '3' key. */
+ public static final int KEYCODE_NUMPAD_3 = 147;
+ /** Key code constant: Numeric keypad '4' key. */
+ public static final int KEYCODE_NUMPAD_4 = 148;
+ /** Key code constant: Numeric keypad '5' key. */
+ public static final int KEYCODE_NUMPAD_5 = 149;
+ /** Key code constant: Numeric keypad '6' key. */
+ public static final int KEYCODE_NUMPAD_6 = 150;
+ /** Key code constant: Numeric keypad '7' key. */
+ public static final int KEYCODE_NUMPAD_7 = 151;
+ /** Key code constant: Numeric keypad '8' key. */
+ public static final int KEYCODE_NUMPAD_8 = 152;
+ /** Key code constant: Numeric keypad '9' key. */
+ public static final int KEYCODE_NUMPAD_9 = 153;
+ /** Key code constant: Numeric keypad '/' key (for division). */
+ public static final int KEYCODE_NUMPAD_DIVIDE = 154;
+ /** Key code constant: Numeric keypad '*' key (for multiplication). */
+ public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
+ /** Key code constant: Numeric keypad '-' key (for subtraction). */
+ public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
+ /** Key code constant: Numeric keypad '+' key (for addition). */
+ public static final int KEYCODE_NUMPAD_ADD = 157;
+ /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
+ public static final int KEYCODE_NUMPAD_DOT = 158;
+ /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
+ public static final int KEYCODE_NUMPAD_COMMA = 159;
+ /** Key code constant: Numeric keypad Enter key. */
+ public static final int KEYCODE_NUMPAD_ENTER = 160;
+ /** Key code constant: Numeric keypad '=' key. */
+ public static final int KEYCODE_NUMPAD_EQUALS = 161;
+ /** Key code constant: Numeric keypad '(' key. */
+ public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
+ /** Key code constant: Numeric keypad ')' key. */
+ public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
+ /** Key code constant: Volume Mute key.
+ * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
+ * This key should normally be implemented as a toggle such that the first press
+ * mutes the speaker and the second press restores the original volume. */
+ public static final int KEYCODE_VOLUME_MUTE = 164;
+ /** Key code constant: Info key.
+ * Common on TV remotes to show additional information related to what is
+ * currently being viewed. */
+ public static final int KEYCODE_INFO = 165;
+ /** Key code constant: Channel up key.
+ * On TV remotes, increments the television channel. */
+ public static final int KEYCODE_CHANNEL_UP = 166;
+ /** Key code constant: Channel down key.
+ * On TV remotes, decrements the television channel. */
+ public static final int KEYCODE_CHANNEL_DOWN = 167;
+ /** Key code constant: Zoom in key. */
+ public static final int KEYCODE_ZOOM_IN = 168;
+ /** Key code constant: Zoom out key. */
+ public static final int KEYCODE_ZOOM_OUT = 169;
+ /** Key code constant: TV key.
+ * On TV remotes, switches to viewing live TV. */
+ public static final int KEYCODE_TV = 170;
+ /** Key code constant: Window key.
+ * On TV remotes, toggles picture-in-picture mode or other windowing functions. */
+ public static final int KEYCODE_WINDOW = 171;
+ /** Key code constant: Guide key.
+ * On TV remotes, shows a programming guide. */
+ public static final int KEYCODE_GUIDE = 172;
+ /** Key code constant: DVR key.
+ * On some TV remotes, switches to a DVR mode for recorded shows. */
+ public static final int KEYCODE_DVR = 173;
+ /** Key code constant: Bookmark key.
+ * On some TV remotes, bookmarks content or web pages. */
+ public static final int KEYCODE_BOOKMARK = 174;
+ /** Key code constant: Toggle captions key.
+ * Switches the mode for closed-captioning text, for example during television shows. */
+ public static final int KEYCODE_CAPTIONS = 175;
+ /** Key code constant: Settings key.
+ * Starts the system settings activity. */
+ public static final int KEYCODE_SETTINGS = 176;
+ /** Key code constant: TV power key.
+ * On TV remotes, toggles the power on a television screen. */
+ public static final int KEYCODE_TV_POWER = 177;
+ /** Key code constant: TV input key.
+ * On TV remotes, switches the input on a television screen. */
+ public static final int KEYCODE_TV_INPUT = 178;
+ /** Key code constant: Set-top-box power key.
+ * On TV remotes, toggles the power on an external Set-top-box. */
+ public static final int KEYCODE_STB_POWER = 179;
+ /** Key code constant: Set-top-box input key.
+ * On TV remotes, switches the input mode on an external Set-top-box. */
+ public static final int KEYCODE_STB_INPUT = 180;
+ /** Key code constant: A/V Receiver power key.
+ * On TV remotes, toggles the power on an external A/V Receiver. */
+ public static final int KEYCODE_AVR_POWER = 181;
+ /** Key code constant: A/V Receiver input key.
+ * On TV remotes, switches the input mode on an external A/V Receiver. */
+ public static final int KEYCODE_AVR_INPUT = 182;
+ /** Key code constant: Red "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_RED = 183;
+ /** Key code constant: Green "programmable" key.
+ * On TV remotes, actsas a contextual/programmable key. */
+ public static final int KEYCODE_PROG_GREEN = 184;
+ /** Key code constant: Yellow "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_YELLOW = 185;
+ /** Key code constant: Blue "programmable" key.
+ * On TV remotes, acts as a contextual/programmable key. */
+ public static final int KEYCODE_PROG_BLUE = 186;
+
+ public static final int LAST_KEYCODE = KEYCODE_PROG_BLUE;
+
+ public static final int META_ALT_ON = 2;
+ public static final int META_CAPS_LOCK_ON = 0x00100000;
+ public static final int META_CTRL_ON = 0x1000;
+ public static final int META_SHIFT_ON = 1;
+ public static final int META_CTRL_MASK = 0x7000;
+ public static final int META_META_ON = 0x00010000;
+ public static final int META_META_MASK = 0x00070000;
+}
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/Patterns.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/Patterns.java
new file mode 100644
index 000000000..f48583967
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/Patterns.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 Jack Palevich
+ *
+ * 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 jackpal.androidterm.emulatorview.compat;
+
+/**
+ * Based upon
+ *
+ * https://android.googlesource.com/platform/frameworks/base/+/android-4.4.2_r2/core/java/android/util/Patterns.java
+ *
+ */
+
+import java.util.regex.Pattern;
+
+public class Patterns {
+ /**
+ * Regular expression to match all IANA top-level domains for WEB_URL.
+ * List accurate as of 2011/07/18. List taken from:
+ * http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+ * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
+ */
+ public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
+ "(?:"
+ + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ + "|(?:biz|b[abdefghijmnorstvwyz])"
+ + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
+ + "|d[ejkmoz]"
+ + "|(?:edu|e[cegrstu])"
+ + "|f[ijkmor]"
+ + "|(?:gov|g[abdefghilmnpqrstuwy])"
+ + "|h[kmnrtu]"
+ + "|(?:info|int|i[delmnoqrst])"
+ + "|(?:jobs|j[emop])"
+ + "|k[eghimnprwyz]"
+ + "|l[abcikrstuvy]"
+ + "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
+ + "|(?:name|net|n[acefgilopruz])"
+ + "|(?:org|om)"
+ + "|(?:pro|p[aefghklmnrstwy])"
+ + "|qa"
+ + "|r[eosuw]"
+ + "|s[abcdeghijklmnortuvyz]"
+ + "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
+ + "|u[agksyz]"
+ + "|v[aceginu]"
+ + "|w[fs]"
+ + "|(?:\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)"
+ + "|y[et]"
+ + "|z[amw]))";
+ /**
+ * Good characters for Internationalized Resource Identifiers (IRI).
+ * This comprises most common used Unicode characters allowed in IRI
+ * as detailed in RFC 3987.
+ * Specifically, those two byte Unicode characters are not included.
+ */
+ public static final String GOOD_IRI_CHAR =
+ "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+ /**
+ * Regular expression pattern to match most part of RFC 3987
+ * Internationalized URLs, aka IRIs. Commonly used Unicode characters are
+ * added.
+ */
+ public static final Pattern WEB_URL = Pattern.compile(
+ "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+ + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+ + "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
+ + TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
+ + "|(?:(?:25[0-5]|2[0-4]" // or ip address
+ + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
+ + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
+ + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ + "|[1-9][0-9]|[0-9])))"
+ + "(?:\\:\\d{1,5})?)" // plus option port number
+ + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+ + "(?:\\b|$)"); // and finally, a word boundary or end of
+ // input. This is to stop foo.sure from
+ // matching as foo.su
+}
\ No newline at end of file
diff --git a/emulatorview/src/main/java/jackpal/androidterm/emulatorview/package.html b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/package.html
new file mode 100644
index 000000000..559ee7c78
--- /dev/null
+++ b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/package.html
@@ -0,0 +1,12 @@
+
+
+
This package provides a fairly complete VT100 terminal emulator {@link
+jackpal.androidterm.emulatorview.TermSession TermSession} and a corresponding
+Android view {@link jackpal.androidterm.emulatorview.EmulatorView EmulatorView}.
+
+
Most users will create a TermSession, connect it to an {@link
+java.io.InputStream InputStream} and {@link java.io.OutputStream OutputStream}
+from the emulation client, then instantiate the EmulatorView and
+add it to an activity's layout.
+
+
diff --git a/res/drawable-hdpi/atari_small.png b/emulatorview/src/main/res/drawable-nodpi/atari_small_nodpi.png
old mode 100755
new mode 100644
similarity index 100%
rename from res/drawable-hdpi/atari_small.png
rename to emulatorview/src/main/res/drawable-nodpi/atari_small_nodpi.png
diff --git a/emulatorview/src/main/res/drawable/atari_small.png b/emulatorview/src/main/res/drawable/atari_small.png
new file mode 100644
index 000000000..8bdd62445
Binary files /dev/null and b/emulatorview/src/main/res/drawable/atari_small.png differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..8c0fb64a8
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..0c71e760d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 000000000..91a7e269e
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 000000000..8a0b282aa
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/jni/Android.mk b/jni/Android.mk
deleted file mode 100644
index b36394288..000000000
--- a/jni/Android.mk
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# Copyright (C) 2008 The Android Open Source Project
-#
-# 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.
-#
-
-# This makefile supplies the rules for building a library of JNI code for
-# use by our example of how to bundle a shared library with an APK.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-# This is the target being built.
-LOCAL_MODULE:= libandroidterm2
-
-
-# All of the source files that we will compile.
-LOCAL_SRC_FILES:= \
- termExec.cpp
-
-# All of the shared libraries we link against.
-LOCAL_SHARED_LIBRARIES := \
- libutils
-
-# No static libraries.
-LOCAL_STATIC_LIBRARIES :=
-
-# Also need the JNI headers.
-LOCAL_C_INCLUDES += \
- $(JNI_H_INCLUDE)
-
-# No special compiler flags.
-LOCAL_CFLAGS +=
-
-# Don't prelink this library. For more efficient code, you may want
-# to add this library to the prelink map and set this to true. However,
-# it's difficult to do this for applications that are not supplied as
-# part of a system image.
-
-LOCAL_PRELINK_MODULE := false
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/termExec.cpp b/jni/termExec.cpp
deleted file mode 100644
index 52f0ac555..000000000
--- a/jni/termExec.cpp
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * 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.
- */
-
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * 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.
- */
-
-#define LOG_TAG "Exec"
-
-#include "jni.h"
-#include
-
-#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
-#define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0)
-#define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-static jclass class_fileDescriptor;
-static jfieldID field_fileDescriptor_descriptor;
-static jmethodID method_fileDescriptor_init;
-
-typedef unsigned short char16_t;
-
-class String8 {
-public:
- String8() {
- mString = 0;
- }
-
- ~String8() {
- if (mString) {
- free(mString);
- }
- }
-
- void set(const char16_t* o, size_t numChars) {
- if (mString) {
- free(mString);
- }
- mString = (char*) malloc(numChars + 1);
- for (size_t i = 0; i < numChars; i++) {
- mString[i] = (char) o[i];
- }
- mString[numChars] = '\0';
- }
-
- const char* string() {
- return mString;
- }
-private:
- char* mString;
-};
-
-static int create_subprocess(const char *cmd, const char *arg0, const char *arg1,
- int* pProcessId)
-{
- char *devname;
- int ptm;
- pid_t pid;
-
- ptm = open("/dev/ptmx", O_RDWR); // | O_NOCTTY);
- if(ptm < 0){
- LOGE("[ cannot open /dev/ptmx - %s ]\n",strerror(errno));
- return -1;
- }
- fcntl(ptm, F_SETFD, FD_CLOEXEC);
-
- if(grantpt(ptm) || unlockpt(ptm) ||
- ((devname = (char*) ptsname(ptm)) == 0)){
- LOGE("[ trouble with /dev/ptmx - %s ]\n", strerror(errno));
- return -1;
- }
-
- pid = fork();
- if(pid < 0) {
- LOGE("- fork failed: %s -\n", strerror(errno));
- return -1;
- }
-
- if(pid == 0){
- close(ptm);
-
- int pts;
-
- setsid();
-
- pts = open(devname, O_RDWR);
- if(pts < 0) exit(-1);
-
- dup2(pts, 0);
- dup2(pts, 1);
- dup2(pts, 2);
-
- execl(cmd, cmd, arg0, arg1, NULL);
- exit(-1);
- } else {
- *pProcessId = (int) pid;
- return ptm;
- }
-}
-
-
-static jobject android_os_Exec_createSubProcess(JNIEnv *env, jobject clazz,
- jstring cmd, jstring arg0, jstring arg1, jintArray processIdArray)
-{
- const jchar* str = cmd ? env->GetStringCritical(cmd, 0) : 0;
- String8 cmd_8;
- if (str) {
- cmd_8.set(str, env->GetStringLength(cmd));
- env->ReleaseStringCritical(cmd, str);
- }
-
- str = arg0 ? env->GetStringCritical(arg0, 0) : 0;
- const char* arg0Str = 0;
- String8 arg0_8;
- if (str) {
- arg0_8.set(str, env->GetStringLength(arg0));
- env->ReleaseStringCritical(arg0, str);
- arg0Str = arg0_8.string();
- }
-
- str = arg1 ? env->GetStringCritical(arg1, 0) : 0;
- const char* arg1Str = 0;
- String8 arg1_8;
- if (str) {
- arg1_8.set(str, env->GetStringLength(arg1));
- env->ReleaseStringCritical(arg1, str);
- arg1Str = arg1_8.string();
- }
-
- int procId;
- int ptm = create_subprocess(cmd_8.string(), arg0Str, arg1Str, &procId);
-
- if (processIdArray) {
- int procIdLen = env->GetArrayLength(processIdArray);
- if (procIdLen > 0) {
- jboolean isCopy;
-
- int* pProcId = (int*) env->GetPrimitiveArrayCritical(processIdArray, &isCopy);
- if (pProcId) {
- *pProcId = procId;
- env->ReleasePrimitiveArrayCritical(processIdArray, pProcId, 0);
- }
- }
- }
-
- jobject result = env->NewObject(class_fileDescriptor, method_fileDescriptor_init);
-
- if (!result) {
- LOGE("Couldn't create a FileDescriptor.");
- }
- else {
- env->SetIntField(result, field_fileDescriptor_descriptor, ptm);
- }
-
- return result;
-}
-
-
-static void android_os_Exec_setPtyWindowSize(JNIEnv *env, jobject clazz,
- jobject fileDescriptor, jint row, jint col, jint xpixel, jint ypixel)
-{
- int fd;
- struct winsize sz;
-
- fd = env->GetIntField(fileDescriptor, field_fileDescriptor_descriptor);
-
- if (env->ExceptionOccurred() != NULL) {
- return;
- }
-
- sz.ws_row = row;
- sz.ws_col = col;
- sz.ws_xpixel = xpixel;
- sz.ws_ypixel = ypixel;
-
- ioctl(fd, TIOCSWINSZ, &sz);
-}
-
-static int android_os_Exec_waitFor(JNIEnv *env, jobject clazz,
- jint procId) {
- int status;
- waitpid(procId, &status, 0);
- int result = 0;
- if (WIFEXITED(status)) {
- result = WEXITSTATUS(status);
- }
- return result;
-}
-
-static void android_os_Exec_close(JNIEnv *env, jobject clazz, jobject fileDescriptor)
-{
- int fd;
- struct winsize sz;
-
- fd = env->GetIntField(fileDescriptor, field_fileDescriptor_descriptor);
-
- if (env->ExceptionOccurred() != NULL) {
- return;
- }
-
- close(fd);
-}
-
-
-static int register_FileDescriptor(JNIEnv *env)
-{
- class_fileDescriptor = env->FindClass("java/io/FileDescriptor");
-
- if (class_fileDescriptor == NULL) {
- LOGE("Can't find java/io/FileDescriptor");
- return -1;
- }
-
- field_fileDescriptor_descriptor = env->GetFieldID(class_fileDescriptor, "descriptor", "I");
-
- if (field_fileDescriptor_descriptor == NULL) {
- LOGE("Can't find FileDescriptor.descriptor");
- return -1;
- }
-
- method_fileDescriptor_init = env->GetMethodID(class_fileDescriptor, "", "()V");
- if (method_fileDescriptor_init == NULL) {
- LOGE("Can't find FileDescriptor.init");
- return -1;
- }
- return 0;
-}
-
-
-static const char *classPathName = "jackpal/androidterm2/Exec";
-
-static JNINativeMethod method_table[] = {
- { "createSubprocess", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I)Ljava/io/FileDescriptor;",
- (void*) android_os_Exec_createSubProcess },
- { "setPtyWindowSize", "(Ljava/io/FileDescriptor;IIII)V",
- (void*) android_os_Exec_setPtyWindowSize},
- { "waitFor", "(I)I",
- (void*) android_os_Exec_waitFor},
- { "close", "(Ljava/io/FileDescriptor;)V",
- (void*) android_os_Exec_close}
-};
-
-/*
- * Register several native methods for one class.
- */
-static int registerNativeMethods(JNIEnv* env, const char* className,
- JNINativeMethod* gMethods, int numMethods)
-{
- jclass clazz;
-
- clazz = env->FindClass(className);
- if (clazz == NULL) {
- LOGE("Native registration unable to find class '%s'", className);
- return JNI_FALSE;
- }
- if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
- LOGE("RegisterNatives failed for '%s'", className);
- return JNI_FALSE;
- }
-
- return JNI_TRUE;
-}
-
-/*
- * Register native methods for all classes we know about.
- *
- * returns JNI_TRUE on success.
- */
-static int registerNatives(JNIEnv* env)
-{
- if (!registerNativeMethods(env, classPathName, method_table,
- sizeof(method_table) / sizeof(method_table[0]))) {
- return JNI_FALSE;
- }
-
- return JNI_TRUE;
-}
-
-
-// ----------------------------------------------------------------------------
-
-/*
- * This is called by the VM when the shared library is first loaded.
- */
-
-typedef union {
- JNIEnv* env;
- void* venv;
-} UnionJNIEnvToVoid;
-
-jint JNI_OnLoad(JavaVM* vm, void* reserved) {
- UnionJNIEnvToVoid uenv;
- uenv.venv = NULL;
- jint result = -1;
- JNIEnv* env = NULL;
-
- LOGI("JNI_OnLoad");
-
- if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
- LOGE("ERROR: GetEnv failed");
- goto bail;
- }
- env = uenv.env;
-
- if ((result = register_FileDescriptor(env)) < 0) {
- LOGE("ERROR: registerFileDescriptor failed");
- goto bail;
- }
-
- if (registerNatives(env) != JNI_TRUE) {
- LOGE("ERROR: registerNatives failed");
- goto bail;
- }
-
- result = JNI_VERSION_1_4;
-
-bail:
- return result;
-}
diff --git a/libtermexec/.gitignore b/libtermexec/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/libtermexec/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/libtermexec/build.gradle b/libtermexec/build.gradle
new file mode 100644
index 000000000..55d9f65f6
--- /dev/null
+++ b/libtermexec/build.gradle
@@ -0,0 +1,59 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.0.0'
+ }
+}
+apply plugin: 'com.android.library'
+
+repositories {
+ jcenter()
+}
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ minSdkVersion 4
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+
+ ndk {
+ moduleName 'libjackpal-termexec2'
+ abiFilters 'all'
+ ldLibs 'log', 'c'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+// by default recent plugin version does not copy any AIDL files "to avoid publishing too much"
+android.libraryVariants.all { variant ->
+ Sync packageAidl = project.tasks.create("addPublic${variant.name.capitalize()}Aidl", Sync) { sync ->
+ from "$project.projectDir/src/main/aidl/"
+ into "$buildDir/intermediates/bundles/${variant.dirName}/aidl/"
+ }
+
+ variant.javaCompile.dependsOn packageAidl
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+
+ compile 'com.android.support:support-annotations:21.0.0'
+}
diff --git a/libtermexec/proguard-rules.pro b/libtermexec/proguard-rules.pro
new file mode 100644
index 000000000..cfa640e43
--- /dev/null
+++ b/libtermexec/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/uniqa/android-sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/libtermexec/src/androidTest/java/jackpal/androidterm/libtermexec/ApplicationTest.java b/libtermexec/src/androidTest/java/jackpal/androidterm/libtermexec/ApplicationTest.java
new file mode 100644
index 000000000..ee475d052
--- /dev/null
+++ b/libtermexec/src/androidTest/java/jackpal/androidterm/libtermexec/ApplicationTest.java
@@ -0,0 +1,13 @@
+package jackpal.androidterm.libtermexec;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
diff --git a/libtermexec/src/main/AndroidManifest.xml b/libtermexec/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..f106d2ed6
--- /dev/null
+++ b/libtermexec/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/libtermexec/src/main/aidl/jackpal/androidterm/libtermexec/v1/ITerminal.aidl b/libtermexec/src/main/aidl/jackpal/androidterm/libtermexec/v1/ITerminal.aidl
new file mode 100644
index 000000000..092738b94
--- /dev/null
+++ b/libtermexec/src/main/aidl/jackpal/androidterm/libtermexec/v1/ITerminal.aidl
@@ -0,0 +1,39 @@
+package jackpal.androidterm.libtermexec.v1;
+
+import android.content.IntentSender;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+
+// see also:
+// the (clumsy) way to handle object inheritance with Binder:
+// https://kevinhartman.github.io/blog/2012/07/23/inheritance-through-ipc-using-aidl-in-android/
+// some (possibly outdated) notes on preserving backward compatibility:
+// https://stackoverflow.com/questions/18197783/android-aidl-interface-parcelables-and-backwards-compatibility
+/**
+ * An interface for interacting with Terminal implementation.
+ *
+ * The version of the interface is encoded in Intent action and the AIDL package name. New versions
+ * of this interface may be implemented in future. Those versions will be made available
+ * in separate packages and older versions will continue to work.
+ */
+interface ITerminal {
+ /**
+ * Start a new Terminal session. A session will remain hosted by service, that provides binding,
+ * but no gurantees of process pesistence as well as stability of connection are made. You
+ * should keep your ParcelFileDescriptor around and allow ServiceConnection to call this method
+ * again, when reconnection happens, in case service hosting the session is killed by system.
+ *
+ * Allows caller to be notified of terminal session events. Multiple calls can happen on each,
+ * and new call types can be introduced, so prepare to ignore unknown event codes.
+ *
+ * So far only notifications about session end (code 0) are supported. This notification is
+ * issued after abovementioned file descriptor is closed and the session is ended from
+ * Terminal's standpoint.
+ *
+ * @param pseudoTerminalMultiplexerFd file descriptor, obtained by opening /dev/ptmx.
+ * @param a callback
+ *
+ * @return IntentSender, that can be used to start corresponding Terminal Activity.
+ */
+ IntentSender startSession(in ParcelFileDescriptor pseudoTerminalMultiplexerFd, in ResultReceiver callback);
+}
diff --git a/libtermexec/src/main/java/jackpal/androidterm/TermExec.java b/libtermexec/src/main/java/jackpal/androidterm/TermExec.java
new file mode 100644
index 000000000..a3e907294
--- /dev/null
+++ b/libtermexec/src/main/java/jackpal/androidterm/TermExec.java
@@ -0,0 +1,132 @@
+package jackpal.androidterm;
+
+import android.annotation.TargetApi;
+import android.os.*;
+import android.support.annotation.NonNull;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.*;
+
+/**
+ * Utility methods for creating and managing a subprocess. This class differs from
+ * {@link java.lang.ProcessBuilder} in that a pty is used to communicate with the subprocess.
+ *
+ * Pseudo-terminals are a powerful Unix feature, that allows programs to interact with other programs
+ * they start in slightly more human-like way. For example, a pty owner can send ^C (aka SIGINT)
+ * to attached shell, even if said shell runs under a different user ID.
+ */
+public class TermExec {
+ // Warning: bump the library revision, when an incompatible change happens
+ static {
+ System.loadLibrary("jackpal-termexec2");
+ }
+
+ public static final String SERVICE_ACTION_V1 = "jackpal.androidterm.action.START_TERM.v1";
+
+ private static Field descriptorField;
+
+ private final List command;
+ private final Map environment;
+
+ public TermExec(@NonNull String... command) {
+ this(new ArrayList<>(Arrays.asList(command)));
+ }
+
+ public TermExec(@NonNull List command) {
+ this.command = command;
+ this.environment = new Hashtable<>(System.getenv());
+ }
+
+ public @NonNull List command() {
+ return command;
+ }
+
+ public @NonNull Map environment() {
+ return environment;
+ }
+
+ public @NonNull TermExec command(@NonNull String... command) {
+ return command(new ArrayList<>(Arrays.asList(command)));
+ }
+
+ public @NonNull TermExec command(List command) {
+ command.clear();
+ command.addAll(command);
+ return this;
+ }
+
+ /**
+ * Start the process and attach it to the pty, corresponding to given file descriptor.
+ * You have to obtain this file descriptor yourself by calling
+ * {@link android.os.ParcelFileDescriptor#open} on special terminal multiplexer
+ * device (located at /dev/ptmx).
+ *
+ * Callers are responsible for closing the descriptor.
+ *
+ * @return the PID of the started process.
+ */
+ public int start(@NonNull ParcelFileDescriptor ptmxFd) throws IOException {
+ if (Looper.getMainLooper() == Looper.myLooper())
+ throw new IllegalStateException("This method must not be called from the main thread!");
+
+ if (command.size() == 0)
+ throw new IllegalStateException("Empty command!");
+
+ final String cmd = command.remove(0);
+ final String[] cmdArray = command.toArray(new String[command.size()]);
+ final String[] envArray = new String[environment.size()];
+ int i = 0;
+ for (Map.Entry entry : environment.entrySet()) {
+ envArray[i++] = entry.getKey() + "=" + entry.getValue();
+ }
+
+ return createSubprocess(ptmxFd, cmd, cmdArray, envArray);
+ }
+
+ /**
+ * Causes the calling thread to wait for the process associated with the
+ * receiver to finish executing.
+ *
+ * @return The exit value of the Process being waited on
+ */
+ public static native int waitFor(int processId);
+
+ /**
+ * Send signal via the "kill" system call. Android {@link android.os.Process#sendSignal} does not
+ * allow negative numbers (denoting process groups) to be used.
+ */
+ public static native void sendSignal(int processId, int signal);
+
+ static int createSubprocess(ParcelFileDescriptor masterFd, String cmd, String[] args, String[] envVars) throws IOException
+ {
+ final int integerFd;
+
+ if (Build.VERSION.SDK_INT >= 12)
+ integerFd = FdHelperHoneycomb.getFd(masterFd);
+ else {
+ try {
+ if (descriptorField == null) {
+ descriptorField = FileDescriptor.class.getDeclaredField("descriptor");
+ descriptorField.setAccessible(true);
+ }
+
+ integerFd = descriptorField.getInt(masterFd.getFileDescriptor());
+ } catch (Exception e) {
+ throw new IOException("Unable to obtain file descriptor on this OS version: " + e.getMessage());
+ }
+ }
+
+ return createSubprocessInternal(cmd, args, envVars, integerFd);
+ }
+
+ private static native int createSubprocessInternal(String cmd, String[] args, String[] envVars, int masterFd);
+}
+
+// prevents runtime errors on old API versions with ruthless verifier
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+class FdHelperHoneycomb {
+ static int getFd(ParcelFileDescriptor descriptor) {
+ return descriptor.getFd();
+ }
+}
diff --git a/libtermexec/src/main/jni/process.cpp b/libtermexec/src/main/jni/process.cpp
new file mode 100644
index 000000000..91459a532
--- /dev/null
+++ b/libtermexec/src/main/jni/process.cpp
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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.
+ */
+
+#include "process.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+typedef unsigned short char16_t;
+
+class String8 {
+public:
+ String8() {
+ mString = 0;
+ }
+
+ ~String8() {
+ if (mString) {
+ free(mString);
+ }
+ }
+
+ void set(const char16_t* o, size_t numChars) {
+ if (mString) {
+ free(mString);
+ }
+ mString = (char*) malloc(numChars + 1);
+ if (!mString) {
+ return;
+ }
+ for (size_t i = 0; i < numChars; i++) {
+ mString[i] = (char) o[i];
+ }
+ mString[numChars] = '\0';
+ }
+
+ const char* string() {
+ return mString;
+ }
+private:
+ char* mString;
+};
+
+static int throwOutOfMemoryError(JNIEnv *env, const char *message)
+{
+ jclass exClass;
+ const char *className = "java/lang/OutOfMemoryError";
+
+ exClass = env->FindClass(className);
+ return env->ThrowNew(exClass, message);
+}
+
+static int throwIOException(JNIEnv *env, int errnum, const char *message)
+{
+ __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "%s errno %s(%d)",
+ message, strerror(errno), errno);
+
+ if (errnum != 0) {
+ const char *s = strerror(errnum);
+ if (strcmp(s, "Unknown error") != 0)
+ message = s;
+ }
+
+ jclass exClass;
+ const char *className = "java/io/IOException";
+
+ exClass = env->FindClass(className);
+ return env->ThrowNew(exClass, message);
+}
+
+static void closeNonstandardFileDescriptors() {
+ // Android uses shared memory to communicate between processes. The file descriptor is passed
+ // to child processes using the environment variable ANDROID_PROPERTY_WORKSPACE, which is of
+ // the form "properties_fd,sizeOfSharedMemory"
+ int properties_fd = -1;
+ char* properties_fd_string = getenv("ANDROID_PROPERTY_WORKSPACE");
+ if (properties_fd_string != NULL) {
+ properties_fd = atoi(properties_fd_string);
+ }
+ DIR *dir = opendir("/proc/self/fd");
+ if(dir != NULL) {
+ int dir_fd = dirfd(dir);
+
+ while(true) {
+ struct dirent *entry = readdir(dir);
+ if(entry == NULL) {
+ break;
+ }
+
+ int fd = atoi(entry->d_name);
+ if(fd > STDERR_FILENO && fd != dir_fd && fd != properties_fd) {
+ close(fd);
+ }
+ }
+
+ closedir(dir);
+ }
+}
+
+static int create_subprocess(JNIEnv *env, const char *cmd, char *const argv[], char *const envp[], int masterFd)
+{
+ // same size as Android 1.6 libc/unistd/ptsname_r.c
+ char devname[64];
+ pid_t pid;
+
+ fcntl(masterFd, F_SETFD, FD_CLOEXEC);
+
+ // grantpt is unnecessary, because we already assume devpts by using /dev/ptmx
+ if(unlockpt(masterFd)){
+ throwIOException(env, errno, "trouble with /dev/ptmx");
+ return -1;
+ }
+ memset(devname, 0, sizeof(devname));
+ // Early (Android 1.6) bionic versions of ptsname_r had a bug where they returned the buffer
+ // instead of 0 on success. A compatible way of telling whether ptsname_r
+ // succeeded is to zero out errno and check it after the call
+ errno = 0;
+ int ptsResult = ptsname_r(masterFd, devname, sizeof(devname));
+ if (ptsResult && errno) {
+ throwIOException(env, errno, "ptsname_r returned error");
+ return -1;
+ }
+
+ pid = fork();
+ if(pid < 0) {
+ throwIOException(env, errno, "fork failed");
+ return -1;
+ }
+
+ if(pid == 0){
+ int pts;
+
+ setsid();
+
+ pts = open(devname, O_RDWR);
+ if(pts < 0) exit(-1);
+
+ ioctl(pts, TIOCSCTTY, 0);
+
+ dup2(pts, 0);
+ dup2(pts, 1);
+ dup2(pts, 2);
+
+ closeNonstandardFileDescriptors();
+
+ if (envp) {
+ for (; *envp; ++envp) {
+ putenv(*envp);
+ }
+ }
+
+ execv(cmd, argv);
+ exit(-1);
+ } else {
+ return (int) pid;
+ }
+}
+
+extern "C" {
+
+JNIEXPORT void JNICALL Java_jackpal_androidterm_TermExec_sendSignal(JNIEnv *env, jobject clazz,
+ jint procId, jint signal)
+{
+ kill(procId, signal);
+}
+
+JNIEXPORT jint JNICALL Java_jackpal_androidterm_TermExec_waitFor(JNIEnv *env, jclass clazz, jint procId) {
+ int status;
+ waitpid(procId, &status, 0);
+ int result = 0;
+ if (WIFEXITED(status)) {
+ result = WEXITSTATUS(status);
+ }
+ return result;
+}
+
+JNIEXPORT jint JNICALL Java_jackpal_androidterm_TermExec_createSubprocessInternal(JNIEnv *env, jclass clazz,
+ jstring cmd, jobjectArray args, jobjectArray envVars, jint masterFd)
+{
+ const jchar* str = cmd ? env->GetStringCritical(cmd, 0) : 0;
+ String8 cmd_8;
+ if (str) {
+ cmd_8.set(str, env->GetStringLength(cmd));
+ env->ReleaseStringCritical(cmd, str);
+ }
+
+ jsize size = args ? env->GetArrayLength(args) : 0;
+ char **argv = NULL;
+ String8 tmp_8;
+ if (size > 0) {
+ argv = (char **)malloc((size+1)*sizeof(char *));
+ if (!argv) {
+ throwOutOfMemoryError(env, "Couldn't allocate argv array");
+ return 0;
+ }
+ for (int i = 0; i < size; ++i) {
+ jstring arg = reinterpret_cast(env->GetObjectArrayElement(args, i));
+ str = env->GetStringCritical(arg, 0);
+ if (!str) {
+ throwOutOfMemoryError(env, "Couldn't get argument from array");
+ return 0;
+ }
+ tmp_8.set(str, env->GetStringLength(arg));
+ env->ReleaseStringCritical(arg, str);
+ argv[i] = strdup(tmp_8.string());
+ }
+ argv[size] = NULL;
+ }
+
+ size = envVars ? env->GetArrayLength(envVars) : 0;
+ char **envp = NULL;
+ if (size > 0) {
+ envp = (char **)malloc((size+1)*sizeof(char *));
+ if (!envp) {
+ throwOutOfMemoryError(env, "Couldn't allocate envp array");
+ return 0;
+ }
+ for (int i = 0; i < size; ++i) {
+ jstring var = reinterpret_cast(env->GetObjectArrayElement(envVars, i));
+ str = env->GetStringCritical(var, 0);
+ if (!str) {
+ throwOutOfMemoryError(env, "Couldn't get env var from array");
+ return 0;
+ }
+ tmp_8.set(str, env->GetStringLength(var));
+ env->ReleaseStringCritical(var, str);
+ envp[i] = strdup(tmp_8.string());
+ }
+ envp[size] = NULL;
+ }
+
+ int ptm = create_subprocess(env, cmd_8.string(), argv, envp, masterFd);
+
+ if (argv) {
+ for (char **tmp = argv; *tmp; ++tmp) {
+ free(*tmp);
+ }
+ free(argv);
+ }
+ if (envp) {
+ for (char **tmp = envp; *tmp; ++tmp) {
+ free(*tmp);
+ }
+ free(envp);
+ }
+
+ return ptm;
+}
+
+}
diff --git a/libtermexec/src/main/jni/process.h b/libtermexec/src/main/jni/process.h
new file mode 100644
index 000000000..60349f6f4
--- /dev/null
+++ b/libtermexec/src/main/jni/process.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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.
+ */
+
+#ifndef _JACKPAL_PROCESS_H
+#define _JACKPAL_PROCESS_H 1
+
+#include
+#include "jni.h"
+#include
+
+#define LOG_TAG "jackpal-termexec"
+
+extern "C" {
+JNIEXPORT jint JNICALL Java_jackpal_androidterm_TermExec_createSubprocessInternal
+ (JNIEnv *, jclass, jstring, jobjectArray, jobjectArray, jint);
+
+ JNIEXPORT jint JNICALL Java_jackpal_androidterm_TermExec_waitFor
+ (JNIEnv *, jclass, jint);
+}
+
+#endif /* !defined(_JACKPAL_PROCESS_H) */
diff --git a/res/drawable-hdpi/app_terminal.png b/res/drawable-hdpi/app_terminal.png
deleted file mode 100755
index 278b2a54f..000000000
Binary files a/res/drawable-hdpi/app_terminal.png and /dev/null differ
diff --git a/res/drawable-mdpi/app_terminal.png b/res/drawable-mdpi/app_terminal.png
deleted file mode 100644
index 1ec3b4b88..000000000
Binary files a/res/drawable-mdpi/app_terminal.png and /dev/null differ
diff --git a/res/drawable-mdpi/atari_small.png b/res/drawable-mdpi/atari_small.png
deleted file mode 100644
index 535e295c8..000000000
Binary files a/res/drawable-mdpi/atari_small.png and /dev/null differ
diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml
deleted file mode 100644
index 1bd3287fd..000000000
--- a/res/values-de/arrays.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
- Statuszeile zeigen
- Statuszeile verbergen
-
-
-
- Permanenter Cursor
- Blinkender Cursor
-
-
-
- Rechteck
- Unterstrichen
- Vertikaler Balken
-
-
-
- 4x8 Pixel
- 6 Punkte
- 7 Punkte
- 8 Punkte
- 9 Punkte
- 10 Punkte
- 12 Punkte
- 14 Punkte
- 16 Punkte
- 20 Punkte
-
-
-
- Schwarzer Text auf weiss
- Weisser Text auf schwarz
- Weisser Text auf blau
- Grüner Text auf schwarz
- Oranger Text auf schwarz
- Roter Text auf schwarz
-
-
-
- Trackball
- \@ Taste
- Linke Alt-Taste
- Rechte Alt-Taste
- Lauter
- Leiser
-
-
-
- Zeichen-basiert
- Wort-basiert
-
-
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
deleted file mode 100644
index 9451a3d01..000000000
--- a/res/values-de/strings.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
- Terminal Emulator
- Einstellungen
- Zurücksetzen
- Email an
- Spezialtasten
- Tastatur an/aus
-
- Text bearbeiten
- Alles kopieren
- Einfügen
-
-
- Bildschirm
-
- Statuszeile
- Die Statuszeile anzeigen/verbergen.
- Statuszeile
-
- Aussehen Cursor
- Das Aussehen des Cursors auswählen.
- Aussehen Cursor
-
- Cursor-Blinken
- Die Art auswählen, wie der Cursor blinken soll.
- Cursor-Blinken
-
- Text
-
- Schriftgröße
- Die Zeichengröße in Punkten auswählen.
- Schriftgröße
-
- Farben
- Die Textfarben auswählen.
- Textfarbe
-
- Tastatur
-
- Steuerungstaste
- Die Steuerungstaste auswählen.
- Steuerungstaste
-
- Eingabemethode
- Die Eingabemethode für die Soft-Tastatur auswählen.
- Eingabemethode
-
- Shell
- Shell
- Die zu verwendene Shell auswählen.
- Shell
-
- Startkommando
- Kommando eingeben, dass beim Start an die Shell gesendet wird.
- Startkommando
-
diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml
deleted file mode 100644
index a7c51b72c..000000000
--- a/res/values-fr/arrays.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
- Afficher la barre de statut
- Masquer la barre de statut
-
-
-
- Curseur non clignotant
- Curseur clignotant
-
-
-
- Rectangle
- Souligné
- Barre verticale
-
-
-
- Texte noir sur blanc
- Texte blanc sur noir
- Texte blanc sur bleu
- Texte vert sur noir
- Texte orange sur noir
- Texte rouge sur noir
-
-
-
- Trackball
- Touche \@
- Touche Alt gauche
- Touche Alt droite
- Touche Vol Haut
- Touche Vol Bas
-
-
-
- Par caractère
- Par mot
-
-
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
deleted file mode 100644
index e133ae518..000000000
--- a/res/values-fr/strings.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
- Terminal Emulator
- Préférences
- RàZ terminal
- Envoyer par e-mail
- Touches spéciales
- Afficher/Masquer Clavier
-
- Modifier le texte
- Tout copier
- Coller
-
-
- Écran
-
- Barre de statut
- Afficher/Masquer la barre de statut
- Barre de statut
-
- Style du curseur
- Choisir le style du curseur
- Style du curseur
-
- Clignotement curseur
- Choisir si le curseur doit clignoter
- Clignotement curseur
-
- Texte
-
- Taille police
- Choisir la taille de la police de caractères en points
- Taille de la police
-
- Couleurs
- Choisir la couleur du texte
- Couleur du texte
-
- Clavier
-
- Touche CTRL
- Choisir quelle touche utiliser pour control (CTRL)
- Touche CTRL
-
- Méthode d\'entrée
- Choisir la méthode d\'entrée du clavier virtuel
- Méthode d\'entrée
-
- Shell
- Ligne de commande
- Régler la ligne de commande du shell
- Shell
-
- Commande initiale
- Envoyée au shell au démarrage
- Commande initiale
-
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
deleted file mode 100644
index 20fbccc4b..000000000
--- a/res/values-it/strings.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
- Emulatore Terminale
- Preferenze
- Resetta term
- Invia Email
- Combinazione tasti
- Attiva/disattiva keyboard
-
- Modifica testo
- Copia tutto
- Incolla
-
-
- Schermo
-
- Status bar
- Mostra/nascondi status bar.
- Status bar
-
- Stile cursore
- Scegli lo stile del cursore.
- Stile cursore
-
- Lampeggio cursore
- Scegli lampeggio cursore.
- Lampeggio cursore
-
- Testo
-
- Dimensione carattere
- Scegli l\'altezza dei caratteri in punti.
- Dimensione carattere
-
- Colori
- Scegli il colore del testo.
- Colore testo
-
- Tastierra
-
- Tasto control
- Scegli tasto control.
- Tasto control
-
- Metodo inserimento
- Scegli il metodo di inserimento per la tastiera virtuale.
- Metodo inserimento
-
- Shell
- Linea di comando
- Specifica la shell.
- Shell
-
- Comando iniziale
- Inviato alla shell al suo avvio.
- Comando iniziale
-
-
diff --git a/res/values-ja/arrays.xml b/res/values-ja/arrays.xml
deleted file mode 100644
index 939a401fa..000000000
--- a/res/values-ja/arrays.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
- ステータスバーを表示する
- ステータスバーを隠す
-
-
-
- 点滅しないカーソル
- 点滅するカーソル
-
-
-
- 四角
- 下線
- 縦線
-
-
-
- 4 x 8ピクセル
- 6 pt
- 7 pt
- 8 pt
- 9 pt
- 10 pt
- 12 pt
- 14 pt
- 16 pt
- 20 pt
-
-
-
- 白背景に黒字
- 黒背景に白字
- 青背景に白字
- 黒背景に緑字
- 黒背景にアンバー字
- 黒背景に赤字
-
-
-
- トラックボール
- \@キー
- 左Altキー
- 右Altキー
- ボリュームアップ
- ボリュームダウン
-
-
-
- 文字ベース
- 単語ベース
-
-
-
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
deleted file mode 100644
index 7c96949ae..000000000
--- a/res/values-ja/strings.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
- 端末エミュレータ
- 設定
- 端末をリセット
- メール送信
- 特殊キー
- ソフトキーボード
-
- テキスト編集
- すべてコピー
- 貼付け
-
-
- スクリーン
-
- ステータスバー
- ステータスバーの表示/非表示
- ステータスバー
-
- カーソルのスタイル
- カーソルスタイルの選択
- カーソルスタイル
-
- カーソルの点滅
- カーソルの点滅を選択
- カーソルの点滅
-
- テキスト
-
- フォントサイズ
- 文字の高さと大きさを選択
- Font size
-
- 色
- 文字の色を選択
- 文字の色
-
- キーボード
-
- コントロールキー
- コントロールキーを選択
- コントロールキー
-
- インプットメソッド
- ソフトキーボードのインプットメソッドを選択
- インプットメソッド
-
- シェル
- コマンドライン
- コマンドラインシェルを指定
- シェル
-
- 初期コマンド
- 開始時、シェルにコマンドを送信する
- 初期コマンド
-
-
diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml
deleted file mode 100644
index 5be72f1e6..000000000
--- a/res/values-ru/arrays.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
- Показывать
- Скрывать
-
-
-
- 4 x 8 пикселей
- 6 pt
- 7 pt
- 8 pt
- 9 pt
- 10 pt
- 12 pt
- 14 pt
- 16 pt
- 20 pt
-
-
-
- Чёрный на белом
- Белый на чёрном
- Белый на синем
- Зелёный на чёрном
- Жёлтый на чёрном
- Красный на чёрном
-
-
-
- Jog ball
- Клавиша \@
- Левый Alt
- Правый Alt
- Громкость вверх
- Громкость вниз
-
-
-
- По знакам
- По словам
-
-
-
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
deleted file mode 100644
index ca1fcfb07..000000000
--- a/res/values-ru/strings.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
- Эмулятор Терминала
- Настройки
- Сбросить терм.
- Отправит Email
- Специальные клавиши
- Экранная клавиатура
- Изменить
- Копировать всё
- Вставить
- Экран
- Статус бар
- Показать/Скрыть статус бар.
- Статус бар
- Текст
- Размер шрифта
- Выберите размер шрифта.
- Размер шрифта
- Цвета
- Выберите цвет текста.
- Цвет текста
- Клавиатура
- Клавиша Control
- Выберите что будет клавишей Control.
- Клавиша Control
- Способ ввода
- Выберите способ ввода для экранной клавиатуры.
- Способ ввода
- Командная оболочка
- Командная строка
- Укажите строку обращения к командной оболочки.
- Оболочка
- Команды запуска
- Передаются облочке при запуске терминала.
- Команды запуска
-
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 6f47b4f0b..000000000
--- a/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
- 终端模拟器
- 首选项
- 重置终端
- 发送电子邮件到...
- 特殊键
- 打开/关闭软键盘
-
- 编辑文本
- 全部复制
- 粘贴
-
-
- 屏幕
-
- 状态栏
- 显示/隐藏状态栏。
- 状态栏
-
- 光标样式
- 选择光标样式
- 光标样式
-
- 光标闪烁
- 选择光标闪烁模式
- 光标闪烁
-
- 文本
-
- 文本大小
- 选择文本大小
- 文本大小
-
- 颜色
- 选择文本颜色
- 文本颜色
-
- 键盘
-
- Ctrl 键
- 设置 Ctrl 键
- Ctrl 键
-
- 输入方式
- 选择输入方式或软键盘
- 输入方式
-
- Shell
- 命令行
- 指定命令行使用的 Shell
- Shell
-
- 初始命令
- 启动 Shell 时自动执行的命令
- 初始命令
-
-
diff --git a/res/values/strings.xml b/res/values/strings.xml
deleted file mode 100644
index a39e26f79..000000000
--- a/res/values/strings.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
- Terminal Emulator
- Preferences
- Reset term
- Email to
- Special keys
- Toggle soft keyboard
-
- Edit text
- Select text
- Copy all
- Paste
-
-
- Screen
-
- Status bar
- Show/hide status bar.
- Status bar
-
- Cursor style
- Choose cursor style.
- Cursor style
-
- Cursor blink
- Choose cursor blink.
- Cursor blink
-
- Text
-
- Font size
- Choose character height in points.
- Font size
-
- Colors
- Choose text color.
- Text color
-
- Keyboard
-
- Control key
- Choose control key.
- Control key
-
- Input method
- Choose input method for soft keyboard.
- Input method
-
- Shell
- Command line
- Specify the shell command line.
- Shell
-
- Initial command
- Sent to the shell when it starts.
- Initial Command
-
-
- 0
- 0
- 0
- 10
- 2
- 0
- 0
- /system/bin/sh -
- export PATH=/data/local/bin:$PATH
-
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
deleted file mode 100644
index df50b011a..000000000
--- a/res/xml/preferences.xml
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/intents/.gitignore b/samples/intents/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/samples/intents/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/samples/intents/build.gradle b/samples/intents/build.gradle
new file mode 100644
index 000000000..0ab284c86
--- /dev/null
+++ b/samples/intents/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "jackpal.androidterm.sample.intents"
+ minSdkVersion 4
+ targetSdkVersion 22
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
diff --git a/samples/intents/lint.xml b/samples/intents/lint.xml
new file mode 100644
index 000000000..45998cd64
--- /dev/null
+++ b/samples/intents/lint.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/samples/intents/src/main/AndroidManifest.xml b/samples/intents/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..1645991cc
--- /dev/null
+++ b/samples/intents/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/intents/src/main/java/jackpal/androidterm/sample/intents/IntentSampleActivity.java b/samples/intents/src/main/java/jackpal/androidterm/sample/intents/IntentSampleActivity.java
new file mode 100644
index 000000000..ed0a173d3
--- /dev/null
+++ b/samples/intents/src/main/java/jackpal/androidterm/sample/intents/IntentSampleActivity.java
@@ -0,0 +1,84 @@
+package jackpal.androidterm.sample.intents;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+
+public class IntentSampleActivity extends Activity
+{
+ private String mHandle;
+ private static final int REQUEST_WINDOW_HANDLE = 1;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ addClickListener(R.id.openNewWindow, new OnClickListener() {
+ public void onClick(View v) {
+ // Intent for opening a new window without providing script
+ Intent intent =
+ new Intent("jackpal.androidterm.OPEN_NEW_WINDOW");
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ startActivity(intent);
+ }});
+
+ final EditText script = (EditText) findViewById(R.id.script);
+ script.setText(getString(R.string.default_script));
+ addClickListener(R.id.runScript, new OnClickListener() {
+ public void onClick(View v) {
+ /* Intent for opening a new window and running the provided
+ script -- you must declare the permission
+ jackpal.androidterm.permission.RUN_SCRIPT in your manifest
+ to use */
+ Intent intent =
+ new Intent("jackpal.androidterm.RUN_SCRIPT");
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ String command = script.getText().toString();
+ intent.putExtra("jackpal.androidterm.iInitialCommand", command);
+ startActivity(intent);
+ }});
+ addClickListener(R.id.runScriptSaveWindow, new OnClickListener() {
+ public void onClick(View v) {
+ /* Intent for running a script in a previously opened window,
+ if it still exists
+ This will open another window if it doesn't find a match */
+ Intent intent =
+ new Intent("jackpal.androidterm.RUN_SCRIPT");
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ String command = script.getText().toString();
+ intent.putExtra("jackpal.androidterm.iInitialCommand", command);
+ if (mHandle != null) {
+ // Identify the targeted window by its handle
+ intent.putExtra("jackpal.androidterm.window_handle",
+ mHandle);
+ }
+ /* The handle for the targeted window -- whether newly opened
+ or reused -- is returned to us via onActivityResult()
+ You can compare it against an existing saved handle to
+ determine whether or not a new window was opened */
+ startActivityForResult(intent, REQUEST_WINDOW_HANDLE);
+ }});
+ }
+
+ private void addClickListener(int buttonId, OnClickListener onClickListener) {
+ ((Button) findViewById(buttonId)).setOnClickListener(onClickListener);
+ }
+
+ protected void onActivityResult(int request, int result, Intent data) {
+ if (result != RESULT_OK) {
+ return;
+ }
+
+ if (request == REQUEST_WINDOW_HANDLE && data != null) {
+ mHandle = data.getStringExtra("jackpal.androidterm.window_handle");
+ ((Button) findViewById(R.id.runScriptSaveWindow)).setText(
+ R.string.run_script_existing_window);
+ }
+ }
+}
diff --git a/samples/intents/src/main/res/layout/main.xml b/samples/intents/src/main/res/layout/main.xml
new file mode 100644
index 000000000..96ea176e4
--- /dev/null
+++ b/samples/intents/src/main/res/layout/main.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/intents/src/main/res/values-ja/strings.xml b/samples/intents/src/main/res/values-ja/strings.xml
new file mode 100644
index 000000000..24d24209f
--- /dev/null
+++ b/samples/intents/src/main/res/values-ja/strings.xml
@@ -0,0 +1,13 @@
+
+
+ ATE Intent サンプル
+ 新しいウインドウを開く
+ スクリプト:
+ スクリプト実行
+ スクリプトを実行しウインドウを保存
+ 保存されたウインドウでスクリプトを実行
+ このサンプルは Android端末エミュレータへインテントを送る方法を示しています.
+
+
+ echo \'Hello, world!\'
+
diff --git a/samples/intents/src/main/res/values-ko/strings.xml b/samples/intents/src/main/res/values-ko/strings.xml
new file mode 100644
index 000000000..90247dd43
--- /dev/null
+++ b/samples/intents/src/main/res/values-ko/strings.xml
@@ -0,0 +1,10 @@
+
+
+ ATE Intent 예제
+ 새로운 창 열기
+ 스크립트 :
+ 스크립트 실행
+ 스크립트 실행 및 명령창 상태 저장
+ 저장된 명령창에서 스크립트 실행
+ 이 예제는 터미널 에뮬레이터에서 어떻게 Intent를 전송하는지 보여줍니다.
+
diff --git a/samples/intents/src/main/res/values/strings.xml b/samples/intents/src/main/res/values/strings.xml
new file mode 100644
index 000000000..c50525e3f
--- /dev/null
+++ b/samples/intents/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+
+
+ TEA Intent Sample
+ Open New Window
+ Script:
+ Run Script
+ Run Script and Save Window
+ Run Script in Saved Window
+ This sample shows how to send intents to Android Terminal Emulator.
+
+
+ echo \'Hello, world!\'
+
diff --git a/samples/pathbroadcasts/.gitignore b/samples/pathbroadcasts/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/samples/pathbroadcasts/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/samples/pathbroadcasts/build.gradle b/samples/pathbroadcasts/build.gradle
new file mode 100644
index 000000000..6993466b5
--- /dev/null
+++ b/samples/pathbroadcasts/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "jackpal.androidterm.sample.pathbroadcasts"
+ minSdkVersion 4
+ targetSdkVersion 22
+
+ ndk {
+ moduleName "libjackpal-androidterm4"
+ ldLibs "log"
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
+
+dependencies {
+ compile project(':emulatorview')
+}
diff --git a/samples/pathbroadcasts/src/main/AndroidManifest.xml b/samples/pathbroadcasts/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..f42d3b078
--- /dev/null
+++ b/samples/pathbroadcasts/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/pathbroadcasts/src/main/assets/hello b/samples/pathbroadcasts/src/main/assets/hello
new file mode 100755
index 000000000..192d7d1d8
--- /dev/null
+++ b/samples/pathbroadcasts/src/main/assets/hello
@@ -0,0 +1,4 @@
+#!/system/bin/sh
+
+echo "Hello, world!"
+exit 0
diff --git a/samples/pathbroadcasts/src/main/assets/ls b/samples/pathbroadcasts/src/main/assets/ls
new file mode 100755
index 000000000..8d9cd25a0
--- /dev/null
+++ b/samples/pathbroadcasts/src/main/assets/ls
@@ -0,0 +1,4 @@
+#!/system/bin/sh
+
+echo "Hello, I'm pretending to be ls!"
+exec /system/bin/ls "$@"
diff --git a/samples/pathbroadcasts/src/main/java/jackpal/androidterm/sample/pathbroadcasts/PathReceiver.java b/samples/pathbroadcasts/src/main/java/jackpal/androidterm/sample/pathbroadcasts/PathReceiver.java
new file mode 100644
index 000000000..22d2a26fa
--- /dev/null
+++ b/samples/pathbroadcasts/src/main/java/jackpal/androidterm/sample/pathbroadcasts/PathReceiver.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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 jackpal.androidterm.sample.pathbroadcasts;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.ProcessBuilder;
+
+public class PathReceiver extends BroadcastReceiver {
+ /**
+ * Called when a broadcast matching the declared intent filters is
+ * received.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ /* Unpack our sample bin/ and sbin/ if not already done */
+ File binDir = setupBinDir(context);
+ File sbinDir = setupSbinDir(context);
+
+ String packageName = context.getPackageName();
+
+ String action = intent.getAction();
+
+ /**
+ * You need to declare the permission
+ * jackpal.androidterm.permission.APPEND_TO_PATH
+ * to receive this broadcast.
+ */
+ if (action.equals("jackpal.androidterm.broadcast.APPEND_TO_PATH")) {
+ /* The directory we want appended goes into the result extras */
+ Bundle result = getResultExtras(true);
+
+ /**
+ * By convention, entries are indexed by package name.
+ *
+ * If you need to impose an ordering constraint for some reason,
+ * you may prepend a number to your package name -- for example,
+ * 50-com.example.awesomebin or 00-net.busybox.android.
+ */
+ result.putString(packageName, binDir.getAbsolutePath());
+
+ setResultCode(Activity.RESULT_OK);
+ }
+
+ /**
+ * You need to declare the permission
+ * jackpal.androidterm.permission.PREPEND_TO_PATH
+ * to receive this broadcast.
+ *
+ * This is intended for packages like BusyBox installers which need
+ * to override existing system commands; otherwise, you should listen
+ * for the APPEND_TO_PATH broadcast instead.
+ */
+ if (action.equals("jackpal.androidterm.broadcast.PREPEND_TO_PATH")) {
+ /* The directory we want prepended goes into the result extras */
+ Bundle result = getResultExtras(true);
+ result.putString(packageName, sbinDir.getAbsolutePath());
+ setResultCode(Activity.RESULT_OK);
+ }
+ }
+
+ private File setupBinDir(Context context) {
+ String dataDir = getDataDir(context);
+ File binDir = new File(dataDir, "bin");
+ if (!binDir.exists()) {
+ try {
+ binDir.mkdir();
+ chmod("755", binDir.getAbsolutePath());
+ } catch (Exception e) {
+ }
+ }
+
+ File hello = new File(binDir, "hello");
+ if (!hello.exists()) {
+ try {
+ InputStream src = context.getAssets().open("hello");
+ FileOutputStream dst = new FileOutputStream(hello);
+ copyStream(dst, src);
+ chmod("755", hello.getAbsolutePath());
+ } catch (Exception e) {
+ }
+ }
+
+ return binDir;
+ }
+
+ private File setupSbinDir(Context context) {
+ String dataDir = getDataDir(context);
+ File sbinDir = new File(dataDir, "sbin");
+ if (!sbinDir.exists()) {
+ try {
+ sbinDir.mkdir();
+ chmod("755", sbinDir.getAbsolutePath());
+ } catch (Exception e) {
+ }
+ }
+
+ File ls = new File(sbinDir, "ls");
+ if (!ls.exists()) {
+ try {
+ InputStream src = context.getAssets().open("ls");
+ FileOutputStream dst = new FileOutputStream(ls);
+ copyStream(dst, src);
+ chmod("755", ls.getAbsolutePath());
+ } catch (Exception e) {
+ }
+ }
+
+ return sbinDir;
+ }
+
+ private String getDataDir(Context context) {
+ /* On API 4 and later, you can just do this */
+ // return context.getApplicationInfo().dataDir;
+
+ String packageName = context.getPackageName();
+ PackageManager pm = context.getPackageManager();
+ String dataDir = null;
+ try {
+ dataDir = pm.getApplicationInfo(packageName, 0).dataDir;
+ } catch (Exception e) {
+ // Won't happen -- we know we're installed
+ }
+ return dataDir;
+ }
+
+ private void copyStream(OutputStream dst, InputStream src) throws IOException {
+ byte[] buffer = new byte[4096];
+ int bytesRead = 0;
+ while ((bytesRead = src.read(buffer)) >= 0) {
+ dst.write(buffer, 0, bytesRead);
+ }
+ dst.close();
+ }
+
+ private void chmod(String... args) throws IOException {
+ String[] cmdline = new String[args.length + 1];
+ cmdline[0] = "/system/bin/chmod";
+ System.arraycopy(args, 0, cmdline, 1, args.length);
+ new ProcessBuilder(cmdline).start();
+ }
+}
diff --git a/samples/pathbroadcasts/src/main/res/values/strings.xml b/samples/pathbroadcasts/src/main/res/values/strings.xml
new file mode 100644
index 000000000..2000cb3e7
--- /dev/null
+++ b/samples/pathbroadcasts/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ TEA Path Broadcast Sample
+
diff --git a/samples/telnet/.gitignore b/samples/telnet/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/samples/telnet/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/samples/telnet/build.gradle b/samples/telnet/build.gradle
new file mode 100644
index 000000000..89ab42754
--- /dev/null
+++ b/samples/telnet/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "jackpal.androidterm.sample.telnet"
+ minSdkVersion 4
+ targetSdkVersion 22
+
+ ndk {
+ moduleName "libjackpal-androidterm4"
+ ldLibs "log"
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
+
+dependencies {
+ compile project(':emulatorview')
+}
diff --git a/samples/telnet/src/main/AndroidManifest.xml b/samples/telnet/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..1417c86d8
--- /dev/null
+++ b/samples/telnet/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/telnet/src/main/java/jackpal/androidterm/sample/telnet/LaunchActivity.java b/samples/telnet/src/main/java/jackpal/androidterm/sample/telnet/LaunchActivity.java
new file mode 100644
index 000000000..a66f941fb
--- /dev/null
+++ b/samples/telnet/src/main/java/jackpal/androidterm/sample/telnet/LaunchActivity.java
@@ -0,0 +1,56 @@
+package jackpal.androidterm.sample.telnet;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+
+/**
+ * Provides a UI to launch the terminal emulator activity, connected to
+ * either a local shell or a Telnet server.
+ */
+public class LaunchActivity extends Activity
+{
+ private static final String TAG = "TelnetLaunchActivity";
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.launch_activity);
+ final Context context = this;
+ addClickListener(R.id.launchLocal, new OnClickListener() {
+ public void onClick(View v) {
+ Intent intent = new Intent(context, TermActivity.class);
+ intent.putExtra("type", "local");
+ startActivity(intent);
+ }});
+
+ final EditText hostEdit = (EditText) findViewById(R.id.hostname);
+ addClickListener(R.id.launchTelnet, new OnClickListener() {
+ public void onClick(View v) {
+ Intent intent = new Intent(context, TermActivity.class);
+ intent.putExtra("type", "telnet");
+ String hostname = hostEdit.getText().toString();
+ intent.putExtra("host", hostname);
+ startActivity(intent);
+ }});
+ }
+
+ private void addClickListener(int buttonId, OnClickListener onClickListener) {
+ ((Button) findViewById(buttonId)).setOnClickListener(onClickListener);
+ }
+}
diff --git a/samples/telnet/src/main/java/jackpal/androidterm/sample/telnet/TelnetSession.java b/samples/telnet/src/main/java/jackpal/androidterm/sample/telnet/TelnetSession.java
new file mode 100644
index 000000000..4f7b6d304
--- /dev/null
+++ b/samples/telnet/src/main/java/jackpal/androidterm/sample/telnet/TelnetSession.java
@@ -0,0 +1,568 @@
+package jackpal.androidterm.sample.telnet;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import android.util.Log;
+
+import jackpal.androidterm.emulatorview.TermSession;
+
+/**
+ * A rudimentary Telnet client implemented as a subclass of TermSession.
+ *
+ * Telnet, as specified in RFC 854, is a fairly simple protocol: for the
+ * most part, we send and receive streams of bytes which can be fed directly
+ * into the terminal emulator. However, there are a handful of complications:
+ *
+ * - The standard says that CR (ASCII carriage return) must either be
+ * translated into the network standard newline sequence CR LF, or be
+ * followed immediately by NUL.
+ * - (byte) 255, called IAC in the standard, introduces Telnet command
+ * sequences which can be used to negotiate options, perform certain
+ * actions on the "Network Virtual Terminal" which the standard defines,
+ * or do flow control.
+ * - By default, the protocol spoken is designed to accommodate a half-duplex
+ * terminal on either end, so we should be able to buffer output and
+ * send it on a trigger (the sequence IAC GA).
+ * - By default, we're expected to be able to echo local keyboard input into
+ * our own output.
+ *
+ * To solve these problems, we filter the input from the network to catch
+ * and implement Telnet commands via the processInput() method. Similarly, we
+ * filter the output from TermSession by overriding write() to modify CR as
+ * required by the standard, and pass a buffer with manually controllable
+ * flushing to the TermSession to use as its output stream.
+ *
+ * In addition to the base Telnet protocol, we implement two options:
+ * the ECHO option (RFC 857) for enabling echoing of input across the network,
+ * and the SUPPRESS-GO-AHEAD option (RFC 858) for omitting half-duplex flow
+ * control. Both of these are commonly available from servers, and make our
+ * lives easier.
+ */
+public class TelnetSession extends TermSession
+{
+ private static final String TAG = "TelnetSession";
+ private static final boolean DEBUG = false;
+
+ public static final int IAC = 255;
+
+ public static final int CMD_SE = 240; // SE -- end of parameters
+ public static final int CMD_NOP = 241; // NOP
+ public static final int CMD_MARK = 242; // data mark
+ public static final int CMD_BRK = 243; // send BREAK to terminal
+ public static final int CMD_IP = 244; // Interrupt Process
+ public static final int CMD_AO = 245; // Abort Output
+ public static final int CMD_AYT = 246; // Are You There
+ public static final int CMD_EC = 247; // Erase Character
+ public static final int CMD_EL = 248; // Erase Line
+ public static final int CMD_GA = 249; // Go Ahead (clear to send)
+ public static final int CMD_SB = 250; // SB -- begin parameters
+ public static final int CMD_WILL = 251; // used in option negotiation
+ public static final int CMD_WONT = 252; // used in option negotiation
+ public static final int CMD_DO = 253; // used in option negotiation
+ public static final int CMD_DONT = 254; // used in option negotiation
+
+ public static final int OPTION_ECHO = 1; // see RFC 857
+ public static final int OPTION_SUPPRESS_GO_AHEAD = 3; // see RFC 858
+ public static final int OPTION_RANDOMLY_LOSE = 256; // see RFC 748 :)
+
+ // Whether we believe the remote end implements the telnet protocol
+ private boolean peerIsTelnetd = false;
+
+ private boolean peerEchoesInput = false;
+ /* RFC 854 says false is the default, but that makes the client effectively
+ useless for connecting to random non-Telnet servers for debugging */
+ private boolean peerSuppressedGoAhead = true;
+
+ private boolean echoInput = false;
+ /* RFC 854 says false is the default, but that makes the client effectively
+ useless for connecting to random non-Telnet servers for debugging */
+ private boolean suppressGoAhead = true;
+ private boolean doSuppressGaRequested = false;
+ private boolean willSuppressGaRequested = false;
+
+ /* Telnet command processor state */
+ private boolean mInTelnetCommand = false;
+ private int mTelnetCommand = 0;
+ private boolean mMultipleParameters = false;
+ private int mLastInputByteProcessed = 0;
+
+ /**
+ * Create a TelnetSession to handle the telnet protocol and terminal
+ * emulation, using an existing InputStream and OutputStream.
+ */
+ public TelnetSession(InputStream termIn, OutputStream termOut) {
+ setTermIn(termIn);
+ setTermOut(termOut);
+ }
+
+ /**
+ * Process data before sending it to the server.
+ * We replace all occurrences of \r with \r\n, as required by the
+ * Telnet protocol (CR meant to be a newline should be sent as CR LF,
+ * and all other CRs must be sent as CR NUL).
+ */
+ @Override
+ public void write(byte[] bytes, int offset, int count) {
+ // Count the number of CRs
+ int numCRs = 0;
+ for (int i = offset; i < offset + count; ++i) {
+ if (bytes[i] == '\r') {
+ ++numCRs;
+ }
+ }
+
+ if (numCRs == 0) {
+ // No CRs -- just send data as-is
+ doWrite(bytes, offset, count);
+
+ if (isRunning() && !peerEchoesInput) {
+ doLocalEcho(bytes);
+ }
+ return;
+ }
+
+ // Convert CRs into CRLFs
+ byte[] translated = new byte[count + numCRs];
+ int j = 0;
+ for (int i = offset; i < offset + count; ++i) {
+ if (bytes[i] == '\r') {
+ translated[j++] = '\r';
+ translated[j++] = '\n';
+ } else {
+ translated[j++] = bytes[i];
+ }
+ }
+
+ // Send the data
+ doWrite(translated, 0, translated.length);
+
+ // If server echo is off, echo the entered characters locally
+ if (isRunning() && !peerEchoesInput) {
+ doLocalEcho(translated);
+ }
+ }
+
+ private byte[] mWriteBuf = new byte[4096];
+ private int mWriteBufLen = 0;
+
+ /* Send data to the server, buffering it first if necessary */
+ private void doWrite(byte[] data, int offset, int count) {
+ if (peerSuppressedGoAhead) {
+ // No need to buffer -- send it straight to the server
+ super.write(data, offset, count);
+ return;
+ }
+
+ /* Flush the buffer if it's full ... not strictly correct, but better
+ than the alternatives */
+ byte[] buffer = mWriteBuf;
+ int bufLen = mWriteBufLen;
+ if (bufLen + count > buffer.length) {
+ flushWriteBuf();
+ bufLen = 0;
+ }
+
+ // Queue the data to be sent at the next server GA
+ System.arraycopy(data, offset, buffer, bufLen, count);
+ mWriteBufLen += count;
+ }
+
+ /* Flush the buffer of data to be written to the server */
+ private void flushWriteBuf() {
+ super.write(mWriteBuf, 0, mWriteBufLen);
+ mWriteBufLen = 0;
+ }
+
+ /* Echoes local input from the emulator back to the emulator screen. */
+ private void doLocalEcho(byte[] data) {
+ if (DEBUG) {
+ Log.d(TAG, "echoing " +
+ Arrays.toString(data) + " back to terminal");
+ }
+ appendToEmulator(data, 0, data.length);
+ notifyUpdate();
+ }
+
+ /**
+ * Input filter which handles Telnet commands and copies data to the
+ * terminal emulator.
+ */
+ @Override
+ public void processInput(byte[] buffer, int offset, int count) {
+ int lastByte = mLastInputByteProcessed;
+ for (int i = offset; i < offset + count; ++i) {
+ // need to interpret the byte as unsigned -- thanks Java!
+ int curByte = ((int) buffer[i]) & 0xff;
+
+ if (DEBUG) {
+ Log.d(TAG, "input byte " + curByte);
+ }
+
+ if (mInTelnetCommand) {
+ // Previous byte was part of a command sequence
+ doTelnetCommand(curByte);
+ lastByte = curByte;
+ continue;
+ }
+
+ switch (curByte) {
+ case IAC: // Telnet command prefix
+ mInTelnetCommand = true;
+ /* Assume we're talking to a real Telnet server */
+ if (!peerIsTelnetd) {
+ doTelnetInit();
+ }
+ break;
+ case CMD_GA: // GA -- clear to send
+ /**
+ * If we're in half-duplex flow control, we've been given
+ * permission to send data; flush our output buffers.
+ *
+ * Note that it's not strictly correct to send the other
+ * side a GA at this point, but since we're not actually
+ * attached to a half-duplex terminal, we don't have a signal
+ * to indicate when the other side should logically begin
+ * to send again.
+ *
+ * In full-duplex operation (option SUPPRESS-GO-AHEAD enabled),
+ * does nothing.
+ */
+ byte[] cmdGa = { (byte) IAC, (byte) CMD_GA };
+ if (!peerSuppressedGoAhead) {
+ if (!suppressGoAhead) {
+ doWrite(cmdGa, 0, cmdGa.length);
+ }
+ flushWriteBuf();
+ }
+ break;
+ case 0: // NUL -- should be ignored following a CR
+ if (lastByte == '\r') {
+ if (echoInput) {
+ // We do need to echo it back to the server, though
+ doEchoInput(0);
+ }
+ break;
+ }
+ default:
+ /* Send the data to the terminal emulator, and echo it back
+ across the network if the other end wants us to do so. */
+ super.processInput(buffer, i, 1);
+ if (echoInput) {
+ doEchoInput(buffer[i]);
+ }
+ }
+ lastByte = curByte;
+ }
+
+ // Save the last byte processed -- we may need it
+ mLastInputByteProcessed = lastByte;
+ }
+
+ byte[] mOneByte = new byte[1];
+ private void doEchoInput(int input) {
+ if (DEBUG) {
+ Log.d(TAG, "echoing " + input + " to remote end");
+ }
+ byte[] oneByte = mOneByte;
+ oneByte[0] = (byte) input;
+ super.write(oneByte, 0, 1);
+ }
+
+ /**
+ * Interpreter for Telnet commands.
+ */
+ private void doTelnetCommand(int curByte) {
+ /* Handle parameter lists */
+ if (mMultipleParameters) {
+ switch (curByte) {
+ case CMD_SE: // SE -- end of parameters
+ doMultiParamCommand();
+ finishTelnetCommand();
+ return;
+ default:
+ addMultiParam(curByte);
+ return;
+ }
+ }
+
+ /* Handle option negotiation */
+ switch (mTelnetCommand) {
+ case CMD_WILL:
+ handleWillOption(curByte);
+ return;
+ case CMD_WONT:
+ handleWontOption(curByte);
+ return;
+ case CMD_DO:
+ handleDoOption(curByte);
+ return;
+ case CMD_DONT:
+ handleDontOption(curByte);
+ return;
+ }
+
+ /* Telnet commands */
+ switch (curByte) {
+ case CMD_EC: // EC -- erase character
+ // ESC [ D (VT100 cursor left)
+ byte[] cmdLeft = { (byte) 27, (byte) '[', (byte) 'D' };
+ // ESC [ P (VT100 erase char at cursor)
+ byte[] cmdErase = { (byte) 27, (byte) '[', (byte) 'P' };
+ super.processInput(cmdLeft, 0, cmdLeft.length);
+ super.processInput(cmdErase, 0, cmdErase.length);
+ break;
+ case CMD_EL: // EL -- erase line
+ // ESC [ 2 K (VT100 clear whole line)
+ byte[] cmdEl = { (byte) 27, (byte) '[', (byte) '2', (byte) 'K' };
+ super.processInput(cmdEl, 0, cmdEl.length);
+ break;
+ case IAC: // send the IAC character to terminal
+ byte[] iac = { (byte) IAC };
+ super.processInput(iac, 0, iac.length);
+ break;
+ case CMD_SB: // SB -- more parameters follow option
+ mMultipleParameters = true;
+ return;
+ case CMD_WILL: // WILL
+ case CMD_WONT: // WON'T
+ case CMD_DO: // DO
+ case CMD_DONT: // DON'T
+ // Option negotiation -- save the command and wait for the option
+ mTelnetCommand = curByte;
+ return;
+ case CMD_AYT: // AYT -- Are You There
+ /**
+ * RFC 854 says we should send back "some visible (i.e., printable)
+ * evidence that the AYT was received" ... this is as good as
+ * anything else
+ */
+ byte[] msg = "yes, I'm here\r\n".getBytes();
+ super.write(msg, 0, msg.length);
+ break;
+ // The following are unimplemented
+ case CMD_MARK: // data mark
+ case CMD_BRK: // send a break to the terminal
+ case CMD_IP: // IP -- interrupt process
+ case CMD_AO: // AO -- abort output
+ case CMD_NOP: // NOP
+ default:
+ break;
+ }
+
+ finishTelnetCommand();
+ }
+
+ // end of command, process next byte normally
+ private void finishTelnetCommand() {
+ mTelnetCommand = 0;
+ mInTelnetCommand = false;
+ mMultipleParameters = false;
+ }
+
+ private void addMultiParam(int curByte) {
+ // unimplemented
+ }
+
+ private void doMultiParamCommand() {
+ // unimplemented
+ }
+
+ /**
+ * Telnet option negotiation code.
+ *
+ * Because the Telnet protocol is defined to be mostly symmetric with
+ * respect to the client and server roles, option negotiation can be
+ * somewhat confusing. The same commands are used to initiate and
+ * respond to negotiation requests, and their exact meaning depends on
+ * whether they were sent as an initial request or as a response:
+ *
+ * - WILL: If sent as a request, indicates that we wish to enable the
+ * option on our end. If sent as a response, indicates that we
+ * have enabled the specified option on our end.
+ * - WON'T: If sent as a request, indicates that we insist on disabling the
+ * option on our end. If sent as a response, indicates that we
+ * refuse to enable the specified option on our end.
+ * - DO: If sent as a request, indicates that we wish the peer to enable
+ * this option on the remote end. If sent as a response, indicates
+ * that we accept the peer's request to enable the option on the
+ * remote end.
+ * - DON'T: If sent as a request, indicates that we demand the peer disable
+ * this option on the remote end. If sent as a response, indicates
+ * that we refuse to allow the peer to enable this option on the
+ * remote end.
+ *
+ * All options are off by default (options have to be explicitly requested).
+ * In order to prevent negotiation loops, we are not supposed to reply to
+ * requests which do not change the state of an option (e.g. if the server
+ * sends DON'T ECHO and we're not echoing back what the server sends us, we
+ * should not reply with WON'T ECHO).
+ *
+ * Examples:
+ *
+ * - server sends WILL ECHO, we reply DO ECHO: the server asks, and we
+ * agree, that the server echo the input we send to it back to us over
+ * the network.
+ * - we send WON'T ECHO, server replies DON'T ECHO: we ask, and the server
+ * agrees, that we not echo the input we receive from the server back to
+ * the server over the network.
+ * - we send DO SUPPRESS-GO-AHEAD, server replies WILL SUPPRESS-GO-AHEAD:
+ * we ask, and the server agrees, that the server not send GA to indicate
+ * when it's ready to take data (in other words, we can freely send data
+ * to the server).
+ * - server sends DO ECHO, we reply WON'T ECHO: the server asks us to
+ * echo the input we receive from it back over the network, but we refuse
+ * to do so.
+ */
+ private void handleWillOption(int curByte) {
+ switch (curByte) {
+ case OPTION_ECHO: // WILL ECHO
+ // We don't ever request DO ECHO, so this must be a request
+ if (!peerEchoesInput) {
+ sendOption(CMD_DO, OPTION_ECHO);
+ }
+ peerEchoesInput = true;
+ break;
+ case OPTION_SUPPRESS_GO_AHEAD: // WILL SUPPRESS-GO-AHEAD
+ if (!doSuppressGaRequested && !peerSuppressedGoAhead) {
+ // This is a request which changes our state, send a reply
+ sendOption(CMD_DO, OPTION_SUPPRESS_GO_AHEAD);
+ }
+ peerSuppressedGoAhead = true;
+ doSuppressGaRequested = false;
+ // Flush unwritten data in the output buffer
+ flushWriteBuf();
+ break;
+ default: // unrecognized option
+ // refuse to let other end enable unknown options
+ sendOption(CMD_DONT, curByte);
+ break;
+ }
+
+ finishTelnetCommand();
+ }
+
+ private void handleWontOption(int curByte) {
+ switch (curByte) {
+ case OPTION_ECHO: // WON'T ECHO
+ // We don't ever request DO ECHO, so this must be a request
+ if (peerEchoesInput) {
+ sendOption(CMD_DONT, OPTION_ECHO);
+ }
+ peerEchoesInput = false;
+ break;
+ case OPTION_SUPPRESS_GO_AHEAD: // WON'T SUPPRESS-GO-AHEAD
+ if (!doSuppressGaRequested && peerSuppressedGoAhead) {
+ // This is a request which changes our state, send a reply
+ sendOption(CMD_DONT, OPTION_SUPPRESS_GO_AHEAD);
+ }
+ peerSuppressedGoAhead = false;
+ doSuppressGaRequested = false;
+ break;
+ default: // unrecognized option
+ // WON'T is the default for any option, so we shouldn't reply
+ break;
+ }
+
+ finishTelnetCommand();
+ }
+
+ private void handleDoOption(int curByte) {
+ switch (curByte) {
+ case OPTION_ECHO: // DO ECHO
+ /* Other Telnet clients like netkit-telnet refuse this request when
+ they receive it, since it doesn't make much sense */
+ sendOption(CMD_WONT, OPTION_ECHO);
+ /**
+ // We don't ever request WILL ECHO, so this must be a request
+ if (!echoInput) {
+ sendOption(CMD_WILL, OPTION_ECHO);
+ }
+ echoInput = true;
+ */
+ break;
+ case OPTION_SUPPRESS_GO_AHEAD: // DO SUPPRESS-GO-AHEAD
+ if (!willSuppressGaRequested && !suppressGoAhead) {
+ // This is a request which changes our state, send a reply
+ sendOption(CMD_WILL, OPTION_SUPPRESS_GO_AHEAD);
+ }
+ suppressGoAhead = true;
+ willSuppressGaRequested = false;
+ break;
+ default: // unrecognized option
+ // refuse to enable unknown options
+ sendOption(CMD_WONT, curByte);
+ break;
+ }
+
+ finishTelnetCommand();
+ }
+
+ private void handleDontOption(int curByte) {
+ switch (curByte) {
+ case OPTION_ECHO: // DON'T ECHO
+ // We don't ever request DON'T ECHO, so this must be a request
+ if (echoInput) {
+ sendOption(CMD_WONT, OPTION_ECHO);
+ }
+ echoInput = false;
+ break;
+ case OPTION_SUPPRESS_GO_AHEAD: // DON'T SUPPRESS-GO-AHEAD
+ if (!willSuppressGaRequested && suppressGoAhead) {
+ // This is a request which changes our state, send a reply
+ sendOption(CMD_WONT, curByte);
+ }
+ suppressGoAhead = false;
+ willSuppressGaRequested = false;
+ break;
+ default: // unrecognized option
+ // DON'T is the default for any option, so we shouldn't reply
+ break;
+ }
+
+ finishTelnetCommand();
+ }
+
+ /* Send an option negotiation command */
+ private void sendOption(int command, int opt) {
+ if (DEBUG) {
+ Log.d(TAG, "sending command: " + command + " " + opt);
+ }
+ // option negotiation needs to bypass the write buffer
+ byte[] buffer = { (byte) IAC, (byte) command, (byte) opt };
+ super.write(buffer, 0, buffer.length);
+ }
+
+ private void requestDoSuppressGoAhead() {
+ doSuppressGaRequested = true;
+ // send IAC DO SUPPRESS-GO-AHEAD
+ sendOption(CMD_DO, OPTION_SUPPRESS_GO_AHEAD);
+ }
+
+ private void requestWillSuppressGoAhead() {
+ willSuppressGaRequested = true;
+ // send IAC WILL SUPPRESS-GO-AHEAD
+ sendOption(CMD_WILL, OPTION_SUPPRESS_GO_AHEAD);
+ }
+
+ /**
+ * Called the first time processInput() encounters IAC in the input stream,
+ * which is a reasonably good heuristic to determine that the other end is
+ * a true Telnet server and not some SMTP/POP/IMAP/whatever server.
+ *
+ * When called, disables the SUPPRESS-GO-AHEAD option for both directions
+ * (required by the standard, but very inconvenient when talking to
+ * non-Telnet servers) and sends requests to reenable it in both directions
+ * (because it's much easier for us when it's on).
+ */
+ private void doTelnetInit() {
+ peerSuppressedGoAhead = false;
+ suppressGoAhead = false;
+
+ requestDoSuppressGoAhead();
+ requestWillSuppressGoAhead();
+
+ peerIsTelnetd = true;
+ }
+}
diff --git a/samples/telnet/src/main/java/jackpal/androidterm/sample/telnet/TermActivity.java b/samples/telnet/src/main/java/jackpal/androidterm/sample/telnet/TermActivity.java
new file mode 100644
index 000000000..b16b84533
--- /dev/null
+++ b/samples/telnet/src/main/java/jackpal/androidterm/sample/telnet/TermActivity.java
@@ -0,0 +1,285 @@
+package jackpal.androidterm.sample.telnet;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.Editable;
+import android.text.method.TextKeyListener;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import jackpal.androidterm.emulatorview.EmulatorView;
+import jackpal.androidterm.emulatorview.TermSession;
+
+/**
+ * This sample activity demonstrates the use of EmulatorView.
+ *
+ * This activity also demonstrates how to set up a simple TermSession connected
+ * to a local program. The Telnet connection demonstrates a more complex case;
+ * see the TelnetSession class for more details.
+ */
+public class TermActivity extends Activity
+{
+ final private static String TAG = "TermActivity";
+ private EditText mEntry;
+ private EmulatorView mEmulatorView;
+ private TermSession mSession;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.term_activity);
+
+ /* Text entry box at the bottom of the activity. Note that you can
+ also send input (whether from a hardware device or soft keyboard)
+ directly to the EmulatorView. */
+ mEntry = (EditText) findViewById(R.id.term_entry);
+ mEntry.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ public boolean onEditorAction(TextView v, int action, KeyEvent ev) {
+ // Ignore enter-key-up events
+ if (ev != null && ev.getAction() == KeyEvent.ACTION_UP) {
+ return false;
+ }
+ // Don't try to send something if we're not connected yet
+ TermSession session = mSession;
+ if (mSession == null) {
+ return true;
+ }
+
+ Editable e = (Editable) v.getText();
+ // Write to the terminal session
+ session.write(e.toString());
+ session.write('\r');
+ TextKeyListener.clear(e);
+ return true;
+ }
+ });
+
+ /* Sends the content of the text entry box to the terminal, without
+ sending a carriage return afterwards */
+ Button sendButton = (Button) findViewById(R.id.term_entry_send);
+ sendButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // Don't try to send something if we're not connected yet
+ TermSession session = mSession;
+ if (mSession == null) {
+ return;
+ }
+ Editable e = (Editable) mEntry.getText();
+ session.write(e.toString());
+ TextKeyListener.clear(e);
+ }
+ });
+
+ /**
+ * EmulatorView setup.
+ */
+ EmulatorView view = (EmulatorView) findViewById(R.id.emulatorView);
+ mEmulatorView = view;
+
+ /* Let the EmulatorView know the screen's density. */
+ DisplayMetrics metrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ view.setDensity(metrics);
+
+ /* Create a TermSession. */
+ Intent myIntent = getIntent();
+ String sessionType = myIntent.getStringExtra("type");
+ TermSession session;
+
+ if (sessionType != null && sessionType.equals("telnet")) {
+ /* Telnet connection: we need to do the network connect on a
+ separate thread, so kick that off and wait for it to finish. */
+ connectToTelnet(myIntent.getStringExtra("host"));
+ return;
+ } else {
+ // Create a local shell session.
+ session = createLocalTermSession();
+ if (session == null) {
+ finish();
+ return;
+ }
+ mSession = session;
+ }
+
+ /* Attach the TermSession to the EmulatorView. */
+ view.attachSession(session);
+
+ /* That's all you have to do! The EmulatorView will call the attached
+ TermSession's initializeEmulator() automatically, once it can
+ calculate the appropriate screen size for the terminal emulator. */
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ /* You should call this to let EmulatorView know that it's visible
+ on screen. */
+ mEmulatorView.onResume();
+
+ mEntry.requestFocus();
+ }
+
+ @Override
+ protected void onPause() {
+ /* You should call this to let EmulatorView know that it's no longer
+ visible on screen. */
+ mEmulatorView.onPause();
+
+ super.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+ /**
+ * Finish the TermSession when we're destroyed. This will free
+ * resources, stop I/O threads, and close the I/O streams attached
+ * to the session.
+ *
+ * For the local session, closing the streams will kill the shell; for
+ * the Telnet session, it closes the network connection.
+ */
+ if (mSession != null) {
+ mSession.finish();
+ }
+
+ super.onDestroy();
+ }
+
+ /**
+ * Create a TermSession connected to a local shell.
+ */
+ private TermSession createLocalTermSession() {
+ /* Instantiate the TermSession ... */
+ TermSession session = new TermSession();
+
+ /* ... create a process ... */
+ /* TODO:Make local session work without execpty.
+ String execPath = LaunchActivity.getDataDir(this) + "/bin/execpty";
+ ProcessBuilder execBuild =
+ new ProcessBuilder(execPath, "/system/bin/sh", "-");
+ */
+ ProcessBuilder execBuild =
+ new ProcessBuilder("/system/bin/sh", "-");
+ execBuild.redirectErrorStream(true);
+ Process exec = null;
+ try {
+ exec = execBuild.start();
+ } catch (Exception e) {
+ Log.e(TAG, "Could not start terminal process.", e);
+ return null;
+ }
+
+ /* ... and connect the process's I/O streams to the TermSession. */
+ session.setTermIn(exec.getInputStream());
+ session.setTermOut(exec.getOutputStream());
+
+ /* You're done! */
+ return session;
+
+ /**
+ * NB: You can invoke a program without using execpty or a native code
+ * method, but the results may not be what you expect, because the
+ * process will be connected to a pipe, not a tty device. tty devices
+ * provide services such as flow control and input/output translation
+ * which many programs expect.
+ *
+ * If you do connect a program directly to a TermSession without using
+ * a tty, you should probably at a minimum translate '\r' (sent by the
+ * Enter key) to '\n' (which most programs expect as their newline
+ * input) in write(), and translate '\n' (sent by most programs to
+ * indicate a newline) to '\r\n' (which the terminal emulator needs to
+ * actually start a new line without overdrawing text or "staircase
+ * effect") in processInput(), before sending it to the terminal
+ * emulator.
+ *
+ * For an example of how to obtain and use a tty device in native code,
+ * see assets-src/execpty.c.
+ */
+ }
+
+ /**
+ * Connect to the Telnet server.
+ */
+ public void connectToTelnet(String server) {
+ String[] telnetServer = server.split(":", 2);
+ final String hostname = telnetServer[0];
+ int port = 23;
+ if (telnetServer.length == 2) {
+ port = Integer.parseInt(telnetServer[1]);
+ }
+ final int portNum = port;
+
+ /* On Android API >= 11 (Honeycomb and later), network operations
+ must be done off the main thread, so kick off a new thread to
+ perform the connect. */
+ new Thread() {
+ public void run() {
+ // Connect to the server
+ try {
+ Socket socket = new Socket(hostname, portNum);
+ mSocket = socket;
+ } catch (IOException e) {
+ Log.e(TAG, "Could not create socket", e);
+ return;
+ }
+
+ // Notify the main thread of the connection
+ mHandler.sendEmptyMessage(MSG_CONNECTED);
+ }
+ }.start();
+ }
+
+ /**
+ * Handler which will receive the message from the Telnet connect thread
+ * that the connection has been established.
+ */
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_CONNECTED) {
+ createTelnetSession();
+ }
+ }
+ };
+ Socket mSocket;
+ private static final int MSG_CONNECTED = 1;
+
+ /* Create the TermSession which will handle the Telnet protocol and
+ terminal emulation. */
+ private void createTelnetSession() {
+ Socket socket = mSocket;
+
+ // Get the socket's input and output streams
+ InputStream termIn;
+ OutputStream termOut;
+ try {
+ termIn = socket.getInputStream();
+ termOut = socket.getOutputStream();
+ } catch (IOException e) {
+ // Handle exception here
+ return;
+ }
+
+ /* Create the TermSession and attach it to the view. See the
+ TelnetSession class for details. */
+ TermSession session = new TelnetSession(termIn, termOut);
+ mEmulatorView.attachSession(session);
+ mSession = session;
+ }
+}
diff --git a/samples/telnet/src/main/res/layout/launch_activity.xml b/samples/telnet/src/main/res/layout/launch_activity.xml
new file mode 100644
index 000000000..ad9b5d0de
--- /dev/null
+++ b/samples/telnet/src/main/res/layout/launch_activity.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/telnet/src/main/res/layout/term_activity.xml b/samples/telnet/src/main/res/layout/term_activity.xml
new file mode 100644
index 000000000..739120217
--- /dev/null
+++ b/samples/telnet/src/main/res/layout/term_activity.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/telnet/src/main/res/values-ko/strings.xml b/samples/telnet/src/main/res/values-ko/strings.xml
new file mode 100644
index 000000000..f0d0df89f
--- /dev/null
+++ b/samples/telnet/src/main/res/values-ko/strings.xml
@@ -0,0 +1,12 @@
+
+
+ 텔넷
+ 쉘 열기
+ 연결
+ 텔넷 경로 :
+ 전송
+ 이 예제는 EmulatorView 위젯과 TermSession 클래스를 통해 텔넷이 어떻게 작동하는지 보여줍니다.
+
+
+ telehack.com
+
diff --git a/samples/telnet/src/main/res/values/strings.xml b/samples/telnet/src/main/res/values/strings.xml
new file mode 100644
index 000000000..a92a93483
--- /dev/null
+++ b/samples/telnet/src/main/res/values/strings.xml
@@ -0,0 +1,12 @@
+
+
+ TEA Telet Sample
+ Open shell
+ Connect
+ Telnet to:
+ Send
+ This example Telnet client demonstrates the use of the EmulatorView widget and the TermSession class.
+
+
+ telehack.com
+
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 000000000..469b130c9
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,5 @@
+include ':emulatorview', ':libtermexec'
+include ':term'
+include ':samples:intents'
+include ':samples:pathbroadcasts'
+include ':samples:telnet'
diff --git a/src/jackpal/androidterm/Exec.java b/src/jackpal/androidterm/Exec.java
deleted file mode 100644
index c1de56f6a..000000000
--- a/src/jackpal/androidterm/Exec.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * 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 jackpal.androidterm2;
-
-import java.io.FileDescriptor;
-
-/**
- * Utility methods for creating and managing a subprocess.
- *
- * Note: The native methods access a package-private
- * java.io.FileDescriptor field to get and set the raw Linux
- * file descriptor. This might break if the implementation of
- * java.io.FileDescriptor is changed.
- */
-
-public class Exec
-{
- static {
- System.loadLibrary("androidterm2");
- }
-
- /**
- * Create a subprocess. Differs from java.lang.ProcessBuilder in
- * that a pty is used to communicate with the subprocess.
- *
- * Callers are responsible for calling Exec.close() on the returned
- * file descriptor.
- *
- * @param cmd The command to execute
- * @param arg0 The first argument to the command, may be null
- * @param arg1 the second argument to the command, may be null
- * @param processId A one-element array to which the process ID of the
- * started process will be written.
- * @return the file descriptor of the started process.
- *
- */
- public static native FileDescriptor createSubprocess(
- String cmd, String arg0, String arg1, int[] processId);
-
- /**
- * Set the widow size for a given pty. Allows programs
- * connected to the pty learn how large their screen is.
- */
- public static native void setPtyWindowSize(FileDescriptor fd,
- int row, int col, int xpixel, int ypixel);
-
- /**
- * Causes the calling thread to wait for the process associated with the
- * receiver to finish executing.
- *
- * @return The exit value of the Process being waited on
- *
- */
- public static native int waitFor(int processId);
-
- /**
- * Close a given file descriptor.
- */
- public static native void close(FileDescriptor fd);
-}
-
diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java
deleted file mode 100644
index 3bb011ce0..000000000
--- a/src/jackpal/androidterm/Term.java
+++ /dev/null
@@ -1,4181 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * 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 jackpal.androidterm2;
-
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.preference.PreferenceManager;
-import android.text.ClipboardManager;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.GestureDetector;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-
-/**
- * A terminal emulator activity.
- */
-
-public class Term extends Activity {
- /**
- * Set to true to add debugging code and logging.
- */
- public static final boolean DEBUG = false;
-
- /**
- * Set to true to log each character received from the remote process to the
- * android log, which makes it easier to debug some kinds of problems with
- * emulating escape sequences and control codes.
- */
- public static final boolean LOG_CHARACTERS_FLAG = DEBUG && false;
-
- /**
- * Set to true to log unknown escape sequences.
- */
- public static final boolean LOG_UNKNOWN_ESCAPE_SEQUENCES = DEBUG && false;
-
- /**
- * The tag we use when logging, so that our messages can be distinguished
- * from other messages in the log. Public because it's used by several
- * classes.
- */
- public static final String LOG_TAG = "Term";
-
- /**
- * Our main view. Displays the emulated terminal screen.
- */
- private EmulatorView mEmulatorView;
-
- /**
- * The pseudo-teletype (pty) file descriptor that we use to communicate with
- * another process, typically a shell.
- */
- private FileDescriptor mTermFd;
-
- /**
- * Used to send data to the remote process.
- */
- private FileOutputStream mTermOut;
-
- /**
- * A key listener that tracks the modifier keys and allows the full ASCII
- * character set to be entered.
- */
- private TermKeyListener mKeyListener;
-
- /**
- * The name of our emulator view in the view resource.
- */
- private static final int EMULATOR_VIEW = R.id.emulatorView;
-
- private int mStatusBar = 0;
- private int mCursorStyle = 0;
- private int mCursorBlink = 0;
- private int mFontSize = 9;
- private int mColorId = 2;
- private int mControlKeyId = 0;
- private int mUseCookedIME = 0;
-
- private static final String STATUSBAR_KEY = "statusbar";
- private static final String CURSORSTYLE_KEY = "cursorstyle";
- private static final String CURSORBLINK_KEY = "cursorblink";
- private static final String FONTSIZE_KEY = "fontsize";
- private static final String COLOR_KEY = "color";
- private static final String CONTROLKEY_KEY = "controlkey";
- private static final String IME_KEY = "ime";
- private static final String SHELL_KEY = "shell";
- private static final String INITIALCOMMAND_KEY = "initialcommand";
-
- public static final int WHITE = 0xffffffff;
- public static final int BLACK = 0xff000000;
- public static final int BLUE = 0xff344ebd;
- public static final int GREEN = 0xff00ff00;
- public static final int AMBER = 0xffffb651;
- public static final int RED = 0xffff0113;
-
- private static final int[][] COLOR_SCHEMES = {
- {BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}, {GREEN, BLACK}, {AMBER, BLACK}, {RED, BLACK}};
-
- private static final int[] CONTROL_KEY_SCHEMES = {
- KeyEvent.KEYCODE_DPAD_CENTER,
- KeyEvent.KEYCODE_AT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_ALT_RIGHT,
- KeyEvent.KEYCODE_VOLUME_UP,
- KeyEvent.KEYCODE_VOLUME_DOWN
- };
- private static final String[] CONTROL_KEY_NAME = {
- "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn"
- };
-
- private int mControlKeyCode;
-
- private final static String DEFAULT_SHELL = "/system/bin/sh -";
- private String mShell;
-
- private final static String DEFAULT_INITIAL_COMMAND =
- "export PATH=/data/local/bin:$PATH";
- private String mInitialCommand;
-
- private SharedPreferences mPrefs;
-
- private final static int SELECT_TEXT_ID = 0;
- private final static int COPY_ALL_ID = 1;
- private final static int PASTE_ID = 2;
-
- private boolean mAlreadyStarted = false;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- Log.e(Term.LOG_TAG, "onCreate");
- mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- readPrefs();
-
- setContentView(R.layout.term_activity);
-
- mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW);
-
- DisplayMetrics metrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(metrics);
- mEmulatorView.setScaledDensity(metrics.scaledDensity);
-
- startListening();
-
- mKeyListener = new TermKeyListener();
-
- mEmulatorView.setFocusable(true);
- mEmulatorView.setFocusableInTouchMode(true);
- mEmulatorView.requestFocus();
- mEmulatorView.register(this, mKeyListener);
-
- registerForContextMenu(mEmulatorView);
-
- updatePrefs();
- mAlreadyStarted = true;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mTermFd != null) {
- Exec.close(mTermFd);
- mTermFd = null;
- }
- }
-
- private void startListening() {
- int[] processId = new int[1];
-
- createSubprocess(processId);
- final int procId = processId[0];
-
- final Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- }
- };
-
- Runnable watchForDeath = new Runnable() {
-
- public void run() {
- Log.i(Term.LOG_TAG, "waiting for: " + procId);
- int result = Exec.waitFor(procId);
- Log.i(Term.LOG_TAG, "Subprocess exited: " + result);
- handler.sendEmptyMessage(result);
- }
-
- };
- Thread watcher = new Thread(watchForDeath);
- watcher.start();
-
- mTermOut = new FileOutputStream(mTermFd);
-
- mEmulatorView.initialize(mTermFd, mTermOut);
-
- sendInitialCommand();
- }
-
- private void sendInitialCommand() {
- String initialCommand = mInitialCommand;
- if (initialCommand == null || initialCommand.equals("")) {
- initialCommand = DEFAULT_INITIAL_COMMAND;
- }
- if (initialCommand.length() > 0) {
- write(initialCommand + '\r');
- }
- }
-
- private void restart() {
- startActivity(getIntent());
- finish();
- }
-
- private void write(String data) {
- try {
- mTermOut.write(data.getBytes());
- mTermOut.flush();
- } catch (IOException e) {
- // Ignore exception
- // We don't really care if the receiver isn't listening.
- // We just make a best effort to answer the query.
- }
- }
-
- private void createSubprocess(int[] processId) {
- String shell = mShell;
- if (shell == null || shell.equals("")) {
- shell = DEFAULT_SHELL;
- }
- ArrayList args = parse(shell);
- String arg0 = args.get(0);
- String arg1 = null;
- String arg2 = null;
- if (args.size() >= 2) {
- arg1 = args.get(1);
- }
- if (args.size() >= 3) {
- arg2 = args.get(2);
- }
- mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId);
- }
-
- private ArrayList parse(String cmd) {
- final int PLAIN = 0;
- final int WHITESPACE = 1;
- final int INQUOTE = 2;
- int state = WHITESPACE;
- ArrayList result = new ArrayList();
- int cmdLen = cmd.length();
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < cmdLen; i++) {
- char c = cmd.charAt(i);
- if (state == PLAIN) {
- if (Character.isWhitespace(c)) {
- result.add(builder.toString());
- builder.delete(0,builder.length());
- state = WHITESPACE;
- } else if (c == '"') {
- state = INQUOTE;
- } else {
- builder.append(c);
- }
- } else if (state == WHITESPACE) {
- if (Character.isWhitespace(c)) {
- // do nothing
- } else if (c == '"') {
- state = INQUOTE;
- } else {
- state = PLAIN;
- builder.append(c);
- }
- } else if (state == INQUOTE) {
- if (c == '\\') {
- if (i + 1 < cmdLen) {
- i += 1;
- builder.append(cmd.charAt(i));
- }
- } else if (c == '"') {
- state = PLAIN;
- } else {
- builder.append(c);
- }
- }
- }
- if (builder.length() > 0) {
- result.add(builder.toString());
- }
- return result;
- }
-
- private void readPrefs() {
- mStatusBar = readIntPref(STATUSBAR_KEY, mStatusBar, 1);
- // mCursorStyle = readIntPref(CURSORSTYLE_KEY, mCursorStyle, 2);
- // mCursorBlink = readIntPref(CURSORBLINK_KEY, mCursorBlink, 1);
- mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 20);
- mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1);
- mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId,
- CONTROL_KEY_SCHEMES.length - 1);
- mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1);
- {
- String newShell = readStringPref(SHELL_KEY, mShell);
- if ((newShell == null) || ! newShell.equals(mShell)) {
- if (mShell != null) {
- Log.i(Term.LOG_TAG, "New shell set. Restarting.");
- restart();
- }
- mShell = newShell;
- }
- }
- {
- String newInitialCommand = readStringPref(INITIALCOMMAND_KEY,
- mInitialCommand);
- if ((newInitialCommand == null)
- || ! newInitialCommand.equals(mInitialCommand)) {
- if (mInitialCommand != null) {
- Log.i(Term.LOG_TAG, "New initial command set. Restarting.");
- restart();
- }
- mInitialCommand = newInitialCommand;
- }
- }
- }
-
- private void updatePrefs() {
- DisplayMetrics metrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(metrics);
- mEmulatorView.setTextSize((int) (mFontSize * metrics.density));
- mEmulatorView.setCursorStyle(mCursorStyle, mCursorBlink);
- mEmulatorView.setUseCookedIME(mUseCookedIME != 0);
- setColors();
- mControlKeyCode = CONTROL_KEY_SCHEMES[mControlKeyId];
- {
- Window win = getWindow();
- WindowManager.LayoutParams params = win.getAttributes();
- final int FULLSCREEN = WindowManager.LayoutParams.FLAG_FULLSCREEN;
- int desiredFlag = mStatusBar != 0 ? 0 : FULLSCREEN;
- if (desiredFlag != (params.flags & FULLSCREEN)) {
- if (mAlreadyStarted) {
- // Can't switch to/from fullscreen after
- // starting the activity.
- restart();
- } else {
- win.setFlags(desiredFlag, FULLSCREEN);
- }
- }
- }
- }
-
- private int readIntPref(String key, int defaultValue, int maxValue) {
- int val;
- try {
- val = Integer.parseInt(
- mPrefs.getString(key, Integer.toString(defaultValue)));
- } catch (NumberFormatException e) {
- val = defaultValue;
- }
- val = Math.max(0, Math.min(val, maxValue));
- return val;
- }
-
- private String readStringPref(String key, String defaultValue) {
- return mPrefs.getString(key, defaultValue);
- }
-
- public int getControlKeyCode() {
- return mControlKeyCode;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- readPrefs();
- updatePrefs();
- mEmulatorView.onResume();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mEmulatorView.onPause();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- mEmulatorView.updateSize(true);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- if (id == R.id.menu_preferences) {
- doPreferences();
- } else if (id == R.id.menu_reset) {
- doResetTerminal();
- } else if (id == R.id.menu_send_email) {
- doEmailTranscript();
- } else if (id == R.id.menu_special_keys) {
- doDocumentKeys();
- } else if (id == R.id.menu_toggle_soft_keyboard) {
- doToggleSoftKeyboard();
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- super.onCreateContextMenu(menu, v, menuInfo);
- menu.setHeaderTitle(R.string.edit_text);
- menu.add(0, SELECT_TEXT_ID, 0, R.string.select_text);
- menu.add(0, COPY_ALL_ID, 0, R.string.copy_all);
- menu.add(0, PASTE_ID, 0, R.string.paste);
- if (!canPaste()) {
- menu.getItem(PASTE_ID).setEnabled(false);
- }
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case SELECT_TEXT_ID:
- mEmulatorView.toggleSelectingText();
- return true;
- case COPY_ALL_ID:
- doCopyAll();
- return true;
- case PASTE_ID:
- doPaste();
- return true;
- default:
- return super.onContextItemSelected(item);
- }
- }
-
- private boolean canPaste() {
- ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
- if (clip.hasText()) {
- return true;
- }
- return false;
- }
-
- private void doPreferences() {
- startActivity(new Intent(this, TermPreferences.class));
- }
-
- private void setColors() {
- int[] scheme = COLOR_SCHEMES[mColorId];
- mEmulatorView.setColors(scheme[0], scheme[1]);
- }
-
- private void doResetTerminal() {
- restart();
- }
-
- private void doEmailTranscript() {
- // Don't really want to supply an address, but
- // currently it's required, otherwise we get an
- // exception.
- String addr = "user@example.com";
- Intent intent =
- new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
- + addr));
-
- intent.putExtra("body", mEmulatorView.getTranscriptText().trim());
- startActivity(intent);
- }
-
- private void doCopyAll() {
- ClipboardManager clip = (ClipboardManager)
- getSystemService(Context.CLIPBOARD_SERVICE);
- clip.setText(mEmulatorView.getTranscriptText().trim());
- }
-
- private void doPaste() {
- ClipboardManager clip = (ClipboardManager)
- getSystemService(Context.CLIPBOARD_SERVICE);
- CharSequence paste = clip.getText();
- byte[] utf8;
- try {
- utf8 = paste.toString().getBytes("UTF-8");
- } catch (UnsupportedEncodingException e) {
- Log.e(Term.LOG_TAG, "UTF-8 encoding not found.");
- return;
- }
- try {
- mTermOut.write(utf8);
- } catch (IOException e) {
- Log.e(Term.LOG_TAG, "could not write paste text to terminal.");
- }
- }
-
- private void doDocumentKeys() {
- String controlKey = CONTROL_KEY_NAME[mControlKeyId];
- new AlertDialog.Builder(this).
- setTitle("Press " + controlKey + " and Key").
- setMessage(controlKey + " Space ==> Control-@ (NUL)\n"
- + controlKey + " A..Z ==> Control-A..Z\n"
- + controlKey + " 1 ==> Control-[ (ESC)\n"
- + controlKey + " 5 ==> Control-_\n"
- + controlKey + " . ==> Control-\\\n"
- + controlKey + " 0 ==> Control-]\n"
- + controlKey + " 6 ==> Control-^").
- show();
- }
-
- private void doToggleSoftKeyboard() {
- InputMethodManager imm = (InputMethodManager)
- getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
-
- }
-}
-
-
-/**
- * An abstract screen interface. A terminal screen stores lines of text. (The
- * reason to abstract it is to allow different implementations, and to hide
- * implementation details from clients.)
- */
-interface Screen {
-
- /**
- * Set line wrap flag for a given row. Affects how lines are logically
- * wrapped when changing screen size or converting to a transcript.
- */
- void setLineWrap(int row);
-
- /**
- * Store byte b into the screen at location (x, y)
- *
- * @param x X coordinate (also known as column)
- * @param y Y coordinate (also known as row)
- * @param b ASCII character to store
- * @param foreColor the foreground color
- * @param backColor the background color
- */
- void set(int x, int y, byte b, int foreColor, int backColor);
-
- /**
- * Scroll the screen down one line. To scroll the whole screen of a 24 line
- * screen, the arguments would be (0, 24).
- *
- * @param topMargin First line that is scrolled.
- * @param bottomMargin One line after the last line that is scrolled.
- */
- void scroll(int topMargin, int bottomMargin, int foreColor, int backColor);
-
- /**
- * Block copy characters from one position in the screen to another. The two
- * positions can overlap. All characters of the source and destination must
- * be within the bounds of the screen, or else an InvalidParemeterException
- * will be thrown.
- *
- * @param sx source X coordinate
- * @param sy source Y coordinate
- * @param w width
- * @param h height
- * @param dx destination X coordinate
- * @param dy destination Y coordinate
- */
- void blockCopy(int sx, int sy, int w, int h, int dx, int dy);
-
- /**
- * Block set characters. All characters must be within the bounds of the
- * screen, or else and InvalidParemeterException will be thrown. Typically
- * this is called with a "val" argument of 32 to clear a block of
- * characters.
- *
- * @param sx source X
- * @param sy source Y
- * @param w width
- * @param h height
- * @param val value to set.
- * @param foreColor the foreground color
- * @param backColor the background color
- */
- void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int
- backColor);
-
- /**
- * Get the contents of the transcript buffer as a text string.
- *
- * @return the contents of the transcript buffer.
- */
- String getTranscriptText();
-
- /**
- * Get the selected text inside transcript buffer as a text string.
- * @param x1 Selection start
- * @param y1 Selection start
- * @param x2 Selection end
- * @param y2 Selection end
- * @return the contents of the transcript buffer.
- */
- String getSelectedText(int x1, int y1, int x2, int y2);
-
- /**
- * Resize the screen
- * @param columns
- * @param rows
- */
- void resize(int columns, int rows, int foreColor, int backColor);
-}
-
-
-/**
- * A TranscriptScreen is a screen that remembers data that's been scrolled. The
- * old data is stored in a ring buffer to minimize the amount of copying that
- * needs to be done. The transcript does its own drawing, to avoid having to
- * expose its internal data structures.
- */
-class TranscriptScreen implements Screen {
- private static final String TAG = "TranscriptScreen";
-
- /**
- * The width of the transcript, in characters. Fixed at initialization.
- */
- private int mColumns;
-
- /**
- * The total number of rows in the transcript and the screen. Fixed at
- * initialization.
- */
- private int mTotalRows;
-
- /**
- * The number of rows in the active portion of the transcript. Doesn't
- * include the screen.
- */
- private int mActiveTranscriptRows;
-
- /**
- * Which row is currently the topmost line of the transcript. Used to
- * implement a circular buffer.
- */
- private int mHead;
-
- /**
- * The number of active rows, includes both the transcript and the screen.
- */
- private int mActiveRows;
-
- /**
- * The number of rows in the screen.
- */
- private int mScreenRows;
-
- /**
- * The data for both the screen and the transcript. The first mScreenRows *
- * mLineWidth characters are the screen, the rest are the transcript.
- * The low byte encodes the ASCII character, the high byte encodes the
- * foreground and background colors, plus underline and bold.
- */
- private char[] mData;
-
- /**
- * The data's stored as color-encoded chars, but the drawing routines require chars, so we
- * need a temporary buffer to hold a row's worth of characters.
- */
- private char[] mRowBuffer;
-
- /**
- * Flags that keep track of whether the current line logically wraps to the
- * next line. This is used when resizing the screen and when copying to the
- * clipboard or an email attachment
- */
-
- private boolean[] mLineWrap;
-
- /**
- * Create a transcript screen.
- *
- * @param columns the width of the screen in characters.
- * @param totalRows the height of the entire text area, in rows of text.
- * @param screenRows the height of just the screen, not including the
- * transcript that holds lines that have scrolled off the top of the
- * screen.
- */
- public TranscriptScreen(int columns, int totalRows, int screenRows,
- int foreColor, int backColor) {
- init(columns, totalRows, screenRows, foreColor, backColor);
- }
-
- private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) {
- mColumns = columns;
- mTotalRows = totalRows;
- mActiveTranscriptRows = 0;
- mHead = 0;
- mActiveRows = screenRows;
- mScreenRows = screenRows;
- int totalSize = columns * totalRows;
- mData = new char[totalSize];
- blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor);
- mRowBuffer = new char[columns];
- mLineWrap = new boolean[totalRows];
- consistencyCheck();
- }
-
- /**
- * Convert a row value from the public external coordinate system to our
- * internal private coordinate system. External coordinate system:
- * -mActiveTranscriptRows to mScreenRows-1, with the screen being
- * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of
- * mData are the visible rows. mScreenRows..mActiveRows - 1 are the
- * transcript, stored as a circular buffer.
- *
- * @param row a row in the external coordinate system.
- * @return The row corresponding to the input argument in the private
- * coordinate system.
- */
- private int externalToInternalRow(int row) {
- if (row < -mActiveTranscriptRows || row >= mScreenRows) {
- String errorMessage = "externalToInternalRow "+ row +
- " " + mActiveTranscriptRows + " " + mScreenRows;
- Log.e(TAG, errorMessage);
- throw new IllegalArgumentException(errorMessage);
- }
- if (row >= 0) {
- return row; // This is a visible row.
- }
- return mScreenRows
- + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows);
- }
-
- private int getOffset(int externalLine) {
- return externalToInternalRow(externalLine) * mColumns;
- }
-
- private int getOffset(int x, int y) {
- return getOffset(y) + x;
- }
-
- public void setLineWrap(int row) {
- mLineWrap[externalToInternalRow(row)] = true;
- }
-
- /**
- * Store byte b into the screen at location (x, y)
- *
- * @param x X coordinate (also known as column)
- * @param y Y coordinate (also known as row)
- * @param b ASCII character to store
- * @param foreColor the foreground color
- * @param backColor the background color
- */
- public void set(int x, int y, byte b, int foreColor, int backColor) {
- mData[getOffset(x, y)] = encode(b, foreColor, backColor);
- }
-
- private char encode(int b, int foreColor, int backColor) {
- return (char) ((foreColor << 12) | (backColor << 8) | b);
- }
-
- /**
- * Scroll the screen down one line. To scroll the whole screen of a 24 line
- * screen, the arguments would be (0, 24).
- *
- * @param topMargin First line that is scrolled.
- * @param bottomMargin One line after the last line that is scrolled.
- */
- public void scroll(int topMargin, int bottomMargin, int foreColor,
- int backColor) {
- // Separate out reasons so that stack crawls help us
- // figure out which condition was violated.
- if (topMargin > bottomMargin - 1) {
- throw new IllegalArgumentException();
- }
-
- if (topMargin > mScreenRows - 1) {
- throw new IllegalArgumentException();
- }
-
- if (bottomMargin > mScreenRows) {
- throw new IllegalArgumentException();
- }
-
- // Adjust the transcript so that the last line of the transcript
- // is ready to receive the newly scrolled data
- consistencyCheck();
- int expansionRows = Math.min(1, mTotalRows - mActiveRows);
- int rollRows = 1 - expansionRows;
- mActiveRows += expansionRows;
- mActiveTranscriptRows += expansionRows;
- if (mActiveTranscriptRows > 0) {
- mHead = (mHead + rollRows) % mActiveTranscriptRows;
- }
- consistencyCheck();
-
- // Block move the scroll line to the transcript
- int topOffset = getOffset(topMargin);
- int destOffset = getOffset(-1);
- System.arraycopy(mData, topOffset, mData, destOffset, mColumns);
-
- int topLine = externalToInternalRow(topMargin);
- int destLine = externalToInternalRow(-1);
- System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1);
-
- // Block move the scrolled data up
- int numScrollChars = (bottomMargin - topMargin - 1) * mColumns;
- System.arraycopy(mData, topOffset + mColumns, mData, topOffset,
- numScrollChars);
- int numScrollLines = (bottomMargin - topMargin - 1);
- System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine,
- numScrollLines);
-
- // Erase the bottom line of the scroll region
- blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor);
- mLineWrap[externalToInternalRow(bottomMargin-1)] = false;
- }
-
- private void consistencyCheck() {
- checkPositive(mColumns);
- checkPositive(mTotalRows);
- checkRange(0, mActiveTranscriptRows, mTotalRows);
- if (mActiveTranscriptRows == 0) {
- checkEqual(mHead, 0);
- } else {
- checkRange(0, mHead, mActiveTranscriptRows-1);
- }
- checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows);
- checkRange(0, mScreenRows, mTotalRows);
-
- checkEqual(mTotalRows, mLineWrap.length);
- checkEqual(mTotalRows*mColumns, mData.length);
- checkEqual(mColumns, mRowBuffer.length);
- }
-
- private void checkPositive(int n) {
- if (n < 0) {
- throw new IllegalArgumentException("checkPositive " + n);
- }
- }
-
- private void checkRange(int a, int b, int c) {
- if (a > b || b > c) {
- throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c);
- }
- }
-
- private void checkEqual(int a, int b) {
- if (a != b) {
- throw new IllegalArgumentException("checkEqual " + a + " == " + b);
- }
- }
-
- /**
- * Block copy characters from one position in the screen to another. The two
- * positions can overlap. All characters of the source and destination must
- * be within the bounds of the screen, or else an InvalidParemeterException
- * will be thrown.
- *
- * @param sx source X coordinate
- * @param sy source Y coordinate
- * @param w width
- * @param h height
- * @param dx destination X coordinate
- * @param dy destination Y coordinate
- */
- public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
- if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows
- || dx < 0 || dx + w > mColumns || dy < 0
- || dy + h > mScreenRows) {
- throw new IllegalArgumentException();
- }
- if (sy <= dy) {
- // Move in increasing order
- for (int y = 0; y < h; y++) {
- int srcOffset = getOffset(sx, sy + y);
- int dstOffset = getOffset(dx, dy + y);
- System.arraycopy(mData, srcOffset, mData, dstOffset, w);
- }
- } else {
- // Move in decreasing order
- for (int y = 0; y < h; y++) {
- int y2 = h - (y + 1);
- int srcOffset = getOffset(sx, sy + y2);
- int dstOffset = getOffset(dx, dy + y2);
- System.arraycopy(mData, srcOffset, mData, dstOffset, w);
- }
- }
- }
-
- /**
- * Block set characters. All characters must be within the bounds of the
- * screen, or else and InvalidParemeterException will be thrown. Typically
- * this is called with a "val" argument of 32 to clear a block of
- * characters.
- *
- * @param sx source X
- * @param sy source Y
- * @param w width
- * @param h height
- * @param val value to set.
- */
- public void blockSet(int sx, int sy, int w, int h, int val,
- int foreColor, int backColor) {
- if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
- throw new IllegalArgumentException();
- }
- char[] data = mData;
- char encodedVal = encode(val, foreColor, backColor);
- for (int y = 0; y < h; y++) {
- int offset = getOffset(sx, sy + y);
- for (int x = 0; x < w; x++) {
- data[offset + x] = encodedVal;
- }
- }
- }
-
- /**
- * Draw a row of text. Out-of-bounds rows are blank, not errors.
- *
- * @param row The row of text to draw.
- * @param canvas The canvas to draw to.
- * @param x The x coordinate origin of the drawing
- * @param y The y coordinate origin of the drawing
- * @param renderer The renderer to use to draw the text
- * @param cx the cursor X coordinate, -1 means don't draw it
- * @param selx1 the text selection start X coordinate
- * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection
- */
- public final void drawText(int row, Canvas canvas, float x, float y,
- TextRenderer renderer, int cx, int selx1, int selx2) {
-
- // Out-of-bounds rows are blank.
- if (row < -mActiveTranscriptRows || row >= mScreenRows) {
- return;
- }
-
- // Copy the data from the byte array to a char array so they can
- // be drawn.
-
- int offset = getOffset(row);
- char[] rowBuffer = mRowBuffer;
- char[] data = mData;
- int columns = mColumns;
- int lastColors = 0;
- int lastRunStart = -1;
- final int CURSOR_MASK = 0x10000;
- for (int i = 0; i < columns; i++) {
- char c = data[offset + i];
- int colors = (char) (c & 0xff00);
- if (cx == i || (i >= selx1 && i <= selx2)) {
- // Set cursor background color:
- colors |= CURSOR_MASK;
- }
- rowBuffer[i] = (char) (c & 0x00ff);
- if (colors != lastColors) {
- if (lastRunStart >= 0) {
- renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
- lastRunStart, i - lastRunStart,
- (lastColors & CURSOR_MASK) != 0,
- 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
- }
- lastColors = colors;
- lastRunStart = i;
- }
- }
- if (lastRunStart >= 0) {
- renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
- lastRunStart, columns - lastRunStart,
- (lastColors & CURSOR_MASK) != 0,
- 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
- }
- }
-
- /**
- * Get the count of active rows.
- *
- * @return the count of active rows.
- */
- public int getActiveRows() {
- return mActiveRows;
- }
-
- /**
- * Get the count of active transcript rows.
- *
- * @return the count of active transcript rows.
- */
- public int getActiveTranscriptRows() {
- return mActiveTranscriptRows;
- }
-
- public String getTranscriptText() {
- return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows);
- }
-
- public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
- return internalGetTranscriptText(true, selX1, selY1, selX2, selY2);
- }
-
- private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) {
- StringBuilder builder = new StringBuilder();
- char[] rowBuffer = mRowBuffer;
- char[] data = mData;
- int columns = mColumns;
- for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) {
- int offset = getOffset(row);
- int lastPrintingChar = -1;
- for (int column = 0; column < columns; column++) {
- char c = data[offset + column];
- if (stripColors) {
- c = (char) (c & 0xff);
- }
- if ((c & 0xff) != ' ') {
- lastPrintingChar = column;
- }
- rowBuffer[column] = c;
- }
- if ( row >= selY1 && row <= selY2 ) {
- int x1 = 0;
- int x2 = 0;
- if ( row == selY1 ) {
- x1 = selX1;
- }
- if ( row == selY2 ) {
- x2 = selX2;
- } else {
- x2 = columns;
- }
- if (mLineWrap[externalToInternalRow(row)]) {
- builder.append(rowBuffer, x1, x2 - x1);
- } else {
- builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1)));
- builder.append('\n');
- }
- }
- }
- return builder.toString();
- }
-
- public void resize(int columns, int rows, int foreColor, int backColor) {
- init(columns, mTotalRows, rows, foreColor, backColor);
- }
-}
-
-/**
- * Renders text into a screen. Contains all the terminal-specific knowlege and
- * state. Emulates a subset of the X Window System xterm terminal, which in turn
- * is an emulator for a subset of the Digital Equipment Corporation vt100
- * terminal. Missing functionality: text attributes (bold, underline, reverse
- * video, color) alternate screen cursor key and keypad escape sequences.
- */
-class TerminalEmulator {
-
- /**
- * The cursor row. Numbered 0..mRows-1.
- */
- private int mCursorRow;
-
- /**
- * The cursor column. Numbered 0..mColumns-1.
- */
- private int mCursorCol;
-
- /**
- * The number of character rows in the terminal screen.
- */
- private int mRows;
-
- /**
- * The number of character columns in the terminal screen.
- */
- private int mColumns;
-
- /**
- * Used to send data to the remote process. Needed to implement the various
- * "report" escape sequences.
- */
- private FileOutputStream mTermOut;
-
- /**
- * Stores the characters that appear on the screen of the emulated terminal.
- */
- private Screen mScreen;
-
- /**
- * Keeps track of the current argument of the current escape sequence.
- * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.)
- */
- private int mArgIndex;
-
- /**
- * The number of parameter arguments. This name comes from the ANSI standard
- * for terminal escape codes.
- */
- private static final int MAX_ESCAPE_PARAMETERS = 16;
-
- /**
- * Holds the arguments of the current escape sequence.
- */
- private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
-
- // Escape processing states:
-
- /**
- * Escape processing state: Not currently in an escape sequence.
- */
- private static final int ESC_NONE = 0;
-
- /**
- * Escape processing state: Have seen an ESC character
- */
- private static final int ESC = 1;
-
- /**
- * Escape processing state: Have seen ESC POUND
- */
- private static final int ESC_POUND = 2;
-
- /**
- * Escape processing state: Have seen ESC and a character-set-select char
- */
- private static final int ESC_SELECT_LEFT_PAREN = 3;
-
- /**
- * Escape processing state: Have seen ESC and a character-set-select char
- */
- private static final int ESC_SELECT_RIGHT_PAREN = 4;
-
- /**
- * Escape processing state: ESC [
- */
- private static final int ESC_LEFT_SQUARE_BRACKET = 5;
-
- /**
- * Escape processing state: ESC [ ?
- */
- private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6;
-
- /**
- * True if the current escape sequence should continue, false if the current
- * escape sequence should be terminated. Used when parsing a single
- * character.
- */
- private boolean mContinueSequence;
-
- /**
- * The current state of the escape sequence state machine.
- */
- private int mEscapeState;
-
- /**
- * Saved state of the cursor row, Used to implement the save/restore cursor
- * position escape sequences.
- */
- private int mSavedCursorRow;
-
- /**
- * Saved state of the cursor column, Used to implement the save/restore
- * cursor position escape sequences.
- */
- private int mSavedCursorCol;
-
- // DecSet booleans
-
- /**
- * This mask indicates 132-column mode is set. (As opposed to 80-column
- * mode.)
- */
- private static final int K_132_COLUMN_MODE_MASK = 1 << 3;
-
- /**
- * This mask indicates that origin mode is set. (Cursor addressing is
- * relative to the absolute screen size, rather than the currently set top
- * and bottom margins.)
- */
- private static final int K_ORIGIN_MODE_MASK = 1 << 6;
-
- /**
- * This mask indicates that wraparound mode is set. (As opposed to
- * stop-at-right-column mode.)
- */
- private static final int K_WRAPAROUND_MODE_MASK = 1 << 7;
-
- /**
- * Holds multiple DECSET flags. The data is stored this way, rather than in
- * separate booleans, to make it easier to implement the save-and-restore
- * semantics. The various k*ModeMask masks can be used to extract and modify
- * the individual flags current states.
- */
- private int mDecFlags;
-
- /**
- * Saves away a snapshot of the DECSET flags. Used to implement save and
- * restore escape sequences.
- */
- private int mSavedDecFlags;
-
- // Modes set with Set Mode / Reset Mode
-
- /**
- * True if insert mode (as opposed to replace mode) is active. In insert
- * mode new characters are inserted, pushing existing text to the right.
- */
- private boolean mInsertMode;
-
- /**
- * Automatic newline mode. Configures whether pressing return on the
- * keyboard automatically generates a return as well. Not currently
- * implemented.
- */
- private boolean mAutomaticNewlineMode;
-
- /**
- * An array of tab stops. mTabStop[i] is true if there is a tab stop set for
- * column i.
- */
- private boolean[] mTabStop;
-
- // The margins allow portions of the screen to be locked.
-
- /**
- * The top margin of the screen, for scrolling purposes. Ranges from 0 to
- * mRows-2.
- */
- private int mTopMargin;
-
- /**
- * The bottom margin of the screen, for scrolling purposes. Ranges from
- * mTopMargin + 2 to mRows. (Defines the first row after the scrolling
- * region.
- */
- private int mBottomMargin;
-
- /**
- * True if the next character to be emitted will be automatically wrapped to
- * the next line. Used to disambiguate the case where the cursor is
- * positioned on column mColumns-1.
- */
- private boolean mAboutToAutoWrap;
-
- /**
- * Used for debugging, counts how many chars have been processed.
- */
- private int mProcessedCharCount;
-
- /**
- * Foreground color, 0..7, mask with 8 for bold
- */
- private int mForeColor;
-
- /**
- * Background color, 0..7, mask with 8 for underline
- */
- private int mBackColor;
-
- private boolean mInverseColors;
-
- private boolean mbKeypadApplicationMode;
-
- private boolean mAlternateCharSet;
-
- /**
- * Used for moving selection up along with the scrolling text
- */
- private int mScrollCounter = 0;
-
- /**
- * Construct a terminal emulator that uses the supplied screen
- *
- * @param screen the screen to render characters into.
- * @param columns the number of columns to emulate
- * @param rows the number of rows to emulate
- * @param termOut the output file descriptor that talks to the pseudo-tty.
- */
- public TerminalEmulator(Screen screen, int columns, int rows,
- FileOutputStream termOut) {
- mScreen = screen;
- mRows = rows;
- mColumns = columns;
- mTabStop = new boolean[mColumns];
- mTermOut = termOut;
- reset();
- }
-
- public void updateSize(int columns, int rows) {
- if (mRows == rows && mColumns == columns) {
- return;
- }
- if (columns <= 0) {
- throw new IllegalArgumentException("rows:" + columns);
- }
-
- if (rows <= 0) {
- throw new IllegalArgumentException("rows:" + rows);
- }
-
- String transcriptText = mScreen.getTranscriptText();
-
- mScreen.resize(columns, rows, mForeColor, mBackColor);
-
- if (mRows != rows) {
- mRows = rows;
- mTopMargin = 0;
- mBottomMargin = mRows;
- }
- if (mColumns != columns) {
- int oldColumns = mColumns;
- mColumns = columns;
- boolean[] oldTabStop = mTabStop;
- mTabStop = new boolean[mColumns];
- int toTransfer = Math.min(oldColumns, columns);
- System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
- while (mCursorCol >= columns) {
- mCursorCol -= columns;
- mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1);
- }
- }
- mCursorRow = 0;
- mCursorCol = 0;
- mAboutToAutoWrap = false;
-
- int end = transcriptText.length()-1;
- while ((end >= 0) && transcriptText.charAt(end) == '\n') {
- end--;
- }
- for(int i = 0; i <= end; i++) {
- byte c = (byte) transcriptText.charAt(i);
- if (c == '\n') {
- setCursorCol(0);
- doLinefeed();
- } else {
- emit(c);
- }
- }
- }
-
- /**
- * Get the cursor's current row.
- *
- * @return the cursor's current row.
- */
- public final int getCursorRow() {
- return mCursorRow;
- }
-
- /**
- * Get the cursor's current column.
- *
- * @return the cursor's current column.
- */
- public final int getCursorCol() {
- return mCursorCol;
- }
-
- public final boolean getKeypadApplicationMode() {
- return mbKeypadApplicationMode;
- }
-
- private void setDefaultTabStops() {
- for (int i = 0; i < mColumns; i++) {
- mTabStop[i] = (i & 7) == 0 && i != 0;
- }
- }
-
- /**
- * Accept bytes (typically from the pseudo-teletype) and process them.
- *
- * @param buffer a byte array containing the bytes to be processed
- * @param base the first index of the array to process
- * @param length the number of bytes in the array to process
- */
- public void append(byte[] buffer, int base, int length) {
- for (int i = 0; i < length; i++) {
- byte b = buffer[base + i];
- try {
- if (Term.LOG_CHARACTERS_FLAG) {
- char printableB = (char) b;
- if (b < 32 || b > 126) {
- printableB = ' ';
- }
- Log.w(Term.LOG_TAG, "'" + Character.toString(printableB)
- + "' (" + Integer.toString(b) + ")");
- }
- process(b);
- mProcessedCharCount++;
- } catch (Exception e) {
- Log.e(Term.LOG_TAG, "Exception while processing character "
- + Integer.toString(mProcessedCharCount) + " code "
- + Integer.toString(b), e);
- }
- }
- }
-
- private void process(byte b) {
- switch (b) {
- case 0: // NUL
- // Do nothing
- break;
-
- case 7: // BEL
- // Do nothing
- break;
-
- case 8: // BS
- setCursorCol(Math.max(0, mCursorCol - 1));
- break;
-
- case 9: // HT
- // Move to next tab stop, but not past edge of screen
- setCursorCol(nextTabStop(mCursorCol));
- break;
-
- case 13:
- setCursorCol(0);
- break;
-
- case 10: // CR
- case 11: // VT
- case 12: // LF
- doLinefeed();
- break;
-
- case 14: // SO:
- setAltCharSet(true);
- break;
-
- case 15: // SI:
- setAltCharSet(false);
- break;
-
-
- case 24: // CAN
- case 26: // SUB
- if (mEscapeState != ESC_NONE) {
- mEscapeState = ESC_NONE;
- emit((byte) 127);
- }
- break;
-
- case 27: // ESC
- // Always starts an escape sequence
- startEscapeSequence(ESC);
- break;
-
- case (byte) 0x9b: // CSI
- startEscapeSequence(ESC_LEFT_SQUARE_BRACKET);
- break;
-
- default:
- mContinueSequence = false;
- switch (mEscapeState) {
- case ESC_NONE:
- if (b >= 32) {
- emit(b);
- }
- break;
-
- case ESC:
- doEsc(b);
- break;
-
- case ESC_POUND:
- doEscPound(b);
- break;
-
- case ESC_SELECT_LEFT_PAREN:
- doEscSelectLeftParen(b);
- break;
-
- case ESC_SELECT_RIGHT_PAREN:
- doEscSelectRightParen(b);
- break;
-
- case ESC_LEFT_SQUARE_BRACKET:
- doEscLeftSquareBracket(b);
- break;
-
- case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
- doEscLSBQuest(b);
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- if (!mContinueSequence) {
- mEscapeState = ESC_NONE;
- }
- break;
- }
- }
-
- private void setAltCharSet(boolean alternateCharSet) {
- mAlternateCharSet = alternateCharSet;
- }
-
- private int nextTabStop(int cursorCol) {
- for (int i = cursorCol; i < mColumns; i++) {
- if (mTabStop[i]) {
- return i;
- }
- }
- return mColumns - 1;
- }
-
- private void doEscLSBQuest(byte b) {
- int mask = getDecFlagsMask(getArg0(0));
- switch (b) {
- case 'h': // Esc [ ? Pn h - DECSET
- mDecFlags |= mask;
- break;
-
- case 'l': // Esc [ ? Pn l - DECRST
- mDecFlags &= ~mask;
- break;
-
- case 'r': // Esc [ ? Pn r - restore
- mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask);
- break;
-
- case 's': // Esc [ ? Pn s - save
- mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask);
- break;
-
- default:
- parseArg(b);
- break;
- }
-
- // 132 column mode
- if ((mask & K_132_COLUMN_MODE_MASK) != 0) {
- // We don't actually set 132 cols, but we do want the
- // side effect of clearing the screen and homing the cursor.
- blockClear(0, 0, mColumns, mRows);
- setCursorRowCol(0, 0);
- }
-
- // origin mode
- if ((mask & K_ORIGIN_MODE_MASK) != 0) {
- // Home the cursor.
- setCursorPosition(0, 0);
- }
- }
-
- private int getDecFlagsMask(int argument) {
- if (argument >= 1 && argument <= 9) {
- return (1 << argument);
- }
-
- return 0;
- }
-
- private void startEscapeSequence(int escapeState) {
- mEscapeState = escapeState;
- mArgIndex = 0;
- for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) {
- mArgs[j] = -1;
- }
- }
-
- private void doLinefeed() {
- int newCursorRow = mCursorRow + 1;
- if (newCursorRow >= mBottomMargin) {
- scroll();
- newCursorRow = mBottomMargin - 1;
- }
- setCursorRow(newCursorRow);
- }
-
- private void continueSequence() {
- mContinueSequence = true;
- }
-
- private void continueSequence(int state) {
- mEscapeState = state;
- mContinueSequence = true;
- }
-
- private void doEscSelectLeftParen(byte b) {
- doSelectCharSet(true, b);
- }
-
- private void doEscSelectRightParen(byte b) {
- doSelectCharSet(false, b);
- }
-
- private void doSelectCharSet(boolean isG0CharSet, byte b) {
- switch (b) {
- case 'A': // United Kingdom character set
- break;
- case 'B': // ASCII set
- break;
- case '0': // Special Graphics
- break;
- case '1': // Alternate character set
- break;
- case '2':
- break;
- default:
- unknownSequence(b);
- }
- }
-
- private void doEscPound(byte b) {
- switch (b) {
- case '8': // Esc # 8 - DECALN alignment test
- mScreen.blockSet(0, 0, mColumns, mRows, 'E',
- getForeColor(), getBackColor());
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- }
-
- private void doEsc(byte b) {
- switch (b) {
- case '#':
- continueSequence(ESC_POUND);
- break;
-
- case '(':
- continueSequence(ESC_SELECT_LEFT_PAREN);
- break;
-
- case ')':
- continueSequence(ESC_SELECT_RIGHT_PAREN);
- break;
-
- case '7': // DECSC save cursor
- mSavedCursorRow = mCursorRow;
- mSavedCursorCol = mCursorCol;
- break;
-
- case '8': // DECRC restore cursor
- setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
- break;
-
- case 'D': // INDEX
- doLinefeed();
- break;
-
- case 'E': // NEL
- setCursorCol(0);
- doLinefeed();
- break;
-
- case 'F': // Cursor to lower-left corner of screen
- setCursorRowCol(0, mBottomMargin - 1);
- break;
-
- case 'H': // Tab set
- mTabStop[mCursorCol] = true;
- break;
-
- case 'M': // Reverse index
- if (mCursorRow == 0) {
- mScreen.blockCopy(0, mTopMargin + 1, mColumns, mBottomMargin
- - (mTopMargin + 1), 0, mTopMargin);
- blockClear(0, mBottomMargin - 1, mColumns);
- } else {
- mCursorRow--;
- }
-
- break;
-
- case 'N': // SS2
- unimplementedSequence(b);
- break;
-
- case '0': // SS3
- unimplementedSequence(b);
- break;
-
- case 'P': // Device control string
- unimplementedSequence(b);
- break;
-
- case 'Z': // return terminal ID
- sendDeviceAttributes();
- break;
-
- case '[':
- continueSequence(ESC_LEFT_SQUARE_BRACKET);
- break;
-
- case '=': // DECKPAM
- mbKeypadApplicationMode = true;
- break;
-
- case '>' : // DECKPNM
- mbKeypadApplicationMode = false;
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- }
-
- private void doEscLeftSquareBracket(byte b) {
- switch (b) {
- case '@': // ESC [ Pn @ - ICH Insert Characters
- {
- int charsAfterCursor = mColumns - mCursorCol;
- int charsToInsert = Math.min(getArg0(1), charsAfterCursor);
- int charsToMove = charsAfterCursor - charsToInsert;
- mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1,
- mCursorCol + charsToInsert, mCursorRow);
- blockClear(mCursorCol, mCursorRow, charsToInsert);
- }
- break;
-
- case 'A': // ESC [ Pn A - Cursor Up
- setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
- break;
-
- case 'B': // ESC [ Pn B - Cursor Down
- setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
- break;
-
- case 'C': // ESC [ Pn C - Cursor Right
- setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1)));
- break;
-
- case 'D': // ESC [ Pn D - Cursor Left
- setCursorCol(Math.max(0, mCursorCol - getArg0(1)));
- break;
-
- case 'G': // ESC [ Pn G - Cursor Horizontal Absolute
- setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
- break;
-
- case 'H': // ESC [ Pn ; H - Cursor Position
- setHorizontalVerticalPosition();
- break;
-
- case 'J': // ESC [ Pn J - Erase in Display
- switch (getArg0(0)) {
- case 0: // Clear below
- blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
- blockClear(0, mCursorRow + 1, mColumns,
- mBottomMargin - (mCursorRow + 1));
- break;
-
- case 1: // Erase from the start of the screen to the cursor.
- blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin);
- blockClear(0, mCursorRow, mCursorCol + 1);
- break;
-
- case 2: // Clear all
- blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin);
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- break;
-
- case 'K': // ESC [ Pn K - Erase in Line
- switch (getArg0(0)) {
- case 0: // Clear to right
- blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
- break;
-
- case 1: // Erase start of line to cursor (including cursor)
- blockClear(0, mCursorRow, mCursorCol + 1);
- break;
-
- case 2: // Clear whole line
- blockClear(0, mCursorRow, mColumns);
- break;
-
- default:
- unknownSequence(b);
- break;
- }
- break;
-
- case 'L': // Insert Lines
- {
- int linesAfterCursor = mBottomMargin - mCursorRow;
- int linesToInsert = Math.min(getArg0(1), linesAfterCursor);
- int linesToMove = linesAfterCursor - linesToInsert;
- mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0,
- mCursorRow + linesToInsert);
- blockClear(0, mCursorRow, mColumns, linesToInsert);
- }
- break;
-
- case 'M': // Delete Lines
- {
- int linesAfterCursor = mBottomMargin - mCursorRow;
- int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
- int linesToMove = linesAfterCursor - linesToDelete;
- mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns,
- linesToMove, 0, mCursorRow);
- blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete);
- }
- break;
-
- case 'P': // Delete Characters
- {
- int charsAfterCursor = mColumns - mCursorCol;
- int charsToDelete = Math.min(getArg0(1), charsAfterCursor);
- int charsToMove = charsAfterCursor - charsToDelete;
- mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow,
- charsToMove, 1, mCursorCol, mCursorRow);
- blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete);
- }
- break;
-
- case 'T': // Mouse tracking
- unimplementedSequence(b);
- break;
-
- case '?': // Esc [ ? -- start of a private mode set
- continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK);
- break;
-
- case 'c': // Send device attributes
- sendDeviceAttributes();
- break;
-
- case 'd': // ESC [ Pn d - Vert Position Absolute
- setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
- break;
-
- case 'f': // Horizontal and Vertical Position
- setHorizontalVerticalPosition();
- break;
-
- case 'g': // Clear tab stop
- switch (getArg0(0)) {
- case 0:
- mTabStop[mCursorCol] = false;
- break;
-
- case 3:
- for (int i = 0; i < mColumns; i++) {
- mTabStop[i] = false;
- }
- break;
-
- default:
- // Specified to have no effect.
- break;
- }
- break;
-
- case 'h': // Set Mode
- doSetMode(true);
- break;
-
- case 'l': // Reset Mode
- doSetMode(false);
- break;
-
- case 'm': // Esc [ Pn m - character attributes.
- selectGraphicRendition();
- break;
-
- case 'r': // Esc [ Pn ; Pn r - set top and bottom margins
- {
- // The top margin defaults to 1, the bottom margin
- // (unusually for arguments) defaults to mRows.
- //
- // The escape sequence numbers top 1..23, but we
- // number top 0..22.
- // The escape sequence numbers bottom 2..24, and
- // so do we (because we use a zero based numbering
- // scheme, but we store the first line below the
- // bottom-most scrolling line.
- // As a result, we adjust the top line by -1, but
- // we leave the bottom line alone.
- //
- // Also require that top + 2 <= bottom
-
- int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
- int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows));
- mTopMargin = top;
- mBottomMargin = bottom;
-
- // The cursor is placed in the home position
- setCursorRowCol(mTopMargin, 0);
- }
- break;
-
- default:
- parseArg(b);
- break;
- }
- }
-
- private void selectGraphicRendition() {
- for (int i = 0; i <= mArgIndex; i++) {
- int code = mArgs[i];
- if ( code < 0) {
- if (mArgIndex > 0) {
- continue;
- } else {
- code = 0;
- }
- }
- if (code == 0) { // reset
- mInverseColors = false;
- mForeColor = 7;
- mBackColor = 0;
- } else if (code == 1) { // bold
- mForeColor |= 0x8;
- } else if (code == 4) { // underscore
- mBackColor |= 0x8;
- } else if (code == 7) { // inverse
- mInverseColors = true;
- } else if (code >= 30 && code <= 37) { // foreground color
- mForeColor = (mForeColor & 0x8) | (code - 30);
- } else if (code >= 40 && code <= 47) { // background color
- mBackColor = (mBackColor & 0x8) | (code - 40);
- } else {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code));
- }
- }
- }
- }
-
- private void blockClear(int sx, int sy, int w) {
- blockClear(sx, sy, w, 1);
- }
-
- private void blockClear(int sx, int sy, int w, int h) {
- mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor());
- }
-
- private int getForeColor() {
- return mInverseColors ?
- ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor;
- }
-
- private int getBackColor() {
- return mInverseColors ?
- ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor;
- }
-
- private void doSetMode(boolean newValue) {
- int modeBit = getArg0(0);
- switch (modeBit) {
- case 4:
- mInsertMode = newValue;
- break;
-
- case 20:
- mAutomaticNewlineMode = newValue;
- break;
-
- default:
- unknownParameter(modeBit);
- break;
- }
- }
-
- private void setHorizontalVerticalPosition() {
-
- // Parameters are Row ; Column
-
- setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
- }
-
- private void setCursorPosition(int x, int y) {
- int effectiveTopMargin = 0;
- int effectiveBottomMargin = mRows;
- if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) {
- effectiveTopMargin = mTopMargin;
- effectiveBottomMargin = mBottomMargin;
- }
- int newRow =
- Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y,
- effectiveBottomMargin - 1));
- int newCol = Math.max(0, Math.min(x, mColumns - 1));
- setCursorRowCol(newRow, newCol);
- }
-
- private void sendDeviceAttributes() {
- // This identifies us as a DEC vt100 with advanced
- // video options. This is what the xterm terminal
- // emulator sends.
- byte[] attributes =
- {
- /* VT100 */
- (byte) 27, (byte) '[', (byte) '?', (byte) '1',
- (byte) ';', (byte) '2', (byte) 'c'
-
- /* VT220
- (byte) 27, (byte) '[', (byte) '?', (byte) '6',
- (byte) '0', (byte) ';',
- (byte) '1', (byte) ';',
- (byte) '2', (byte) ';',
- (byte) '6', (byte) ';',
- (byte) '8', (byte) ';',
- (byte) '9', (byte) ';',
- (byte) '1', (byte) '5', (byte) ';',
- (byte) 'c'
- */
- };
-
- write(attributes);
- }
-
- /**
- * Send data to the shell process
- * @param data
- */
- private void write(byte[] data) {
- try {
- mTermOut.write(data);
- mTermOut.flush();
- } catch (IOException e) {
- // Ignore exception
- // We don't really care if the receiver isn't listening.
- // We just make a best effort to answer the query.
- }
- }
-
- private void scroll() {
- //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin);
- mScrollCounter ++;
- mScreen.scroll(mTopMargin, mBottomMargin,
- getForeColor(), getBackColor());
- }
-
- /**
- * Process the next ASCII character of a parameter.
- *
- * @param b The next ASCII character of the paramater sequence.
- */
- private void parseArg(byte b) {
- if (b >= '0' && b <= '9') {
- if (mArgIndex < mArgs.length) {
- int oldValue = mArgs[mArgIndex];
- int thisDigit = b - '0';
- int value;
- if (oldValue >= 0) {
- value = oldValue * 10 + thisDigit;
- } else {
- value = thisDigit;
- }
- mArgs[mArgIndex] = value;
- }
- continueSequence();
- } else if (b == ';') {
- if (mArgIndex < mArgs.length) {
- mArgIndex++;
- }
- continueSequence();
- } else {
- unknownSequence(b);
- }
- }
-
- private int getArg0(int defaultValue) {
- return getArg(0, defaultValue);
- }
-
- private int getArg1(int defaultValue) {
- return getArg(1, defaultValue);
- }
-
- private int getArg(int index, int defaultValue) {
- int result = mArgs[index];
- if (result < 0) {
- result = defaultValue;
- }
- return result;
- }
-
- private void unimplementedSequence(byte b) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- logError("unimplemented", b);
- }
- finishSequence();
- }
-
- private void unknownSequence(byte b) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- logError("unknown", b);
- }
- finishSequence();
- }
-
- private void unknownParameter(int parameter) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- StringBuilder buf = new StringBuilder();
- buf.append("Unknown parameter");
- buf.append(parameter);
- logError(buf.toString());
- }
- }
-
- private void logError(String errorType, byte b) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- StringBuilder buf = new StringBuilder();
- buf.append(errorType);
- buf.append(" sequence ");
- buf.append(" EscapeState: ");
- buf.append(mEscapeState);
- buf.append(" char: '");
- buf.append((char) b);
- buf.append("' (");
- buf.append(b);
- buf.append(")");
- boolean firstArg = true;
- for (int i = 0; i <= mArgIndex; i++) {
- int value = mArgs[i];
- if (value >= 0) {
- if (firstArg) {
- firstArg = false;
- buf.append("args = ");
- }
- buf.append(String.format("%d; ", value));
- }
- }
- logError(buf.toString());
- }
- }
-
- private void logError(String error) {
- if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
- Log.e(Term.LOG_TAG, error);
- }
- finishSequence();
- }
-
- private void finishSequence() {
- mEscapeState = ESC_NONE;
- }
-
- private boolean autoWrapEnabled() {
- // Always enable auto wrap, because it's useful on a small screen
- return true;
- // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0;
- }
-
- /**
- * Send an ASCII character to the screen.
- *
- * @param b the ASCII character to display.
- */
- private void emit(byte b) {
- boolean autoWrap = autoWrapEnabled();
-
- if (autoWrap) {
- if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) {
- mScreen.setLineWrap(mCursorRow);
- mCursorCol = 0;
- if (mCursorRow + 1 < mBottomMargin) {
- mCursorRow++;
- } else {
- scroll();
- }
- }
- }
-
- if (mInsertMode) { // Move character to right one space
- int destCol = mCursorCol + 1;
- if (destCol < mColumns) {
- mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol,
- 1, destCol, mCursorRow);
- }
- }
-
- mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor());
-
- if (autoWrap) {
- mAboutToAutoWrap = (mCursorCol == mColumns - 1);
- }
-
- mCursorCol = Math.min(mCursorCol + 1, mColumns - 1);
- }
-
- private void setCursorRow(int row) {
- mCursorRow = row;
- mAboutToAutoWrap = false;
- }
-
- private void setCursorCol(int col) {
- mCursorCol = col;
- mAboutToAutoWrap = false;
- }
-
- private void setCursorRowCol(int row, int col) {
- mCursorRow = Math.min(row, mRows-1);
- mCursorCol = Math.min(col, mColumns-1);
- mAboutToAutoWrap = false;
- }
-
- public int getScrollCounter() {
- return mScrollCounter;
- }
-
- public void clearScrollCounter() {
- mScrollCounter = 0;
- }
-
- /**
- * Reset the terminal emulator to its initial state.
- */
- public void reset() {
- mCursorRow = 0;
- mCursorCol = 0;
- mArgIndex = 0;
- mContinueSequence = false;
- mEscapeState = ESC_NONE;
- mSavedCursorRow = 0;
- mSavedCursorCol = 0;
- mDecFlags = 0;
- mSavedDecFlags = 0;
- mInsertMode = false;
- mAutomaticNewlineMode = false;
- mTopMargin = 0;
- mBottomMargin = mRows;
- mAboutToAutoWrap = false;
- mForeColor = 7;
- mBackColor = 0;
- mInverseColors = false;
- mbKeypadApplicationMode = false;
- mAlternateCharSet = false;
- // mProcessedCharCount is preserved unchanged.
- setDefaultTabStops();
- blockClear(0, 0, mColumns, mRows);
- }
-
- public String getTranscriptText() {
- return mScreen.getTranscriptText();
- }
- public String getSelectedText(int x1, int y1, int x2, int y2) {
- return mScreen.getSelectedText(x1, y1, x2, y2);
- }
-}
-
-/**
- * Text renderer interface
- */
-
-interface TextRenderer {
- int getCharacterWidth();
- int getCharacterHeight();
- void drawTextRun(Canvas canvas, float x, float y,
- int lineOffset, char[] text,
- int index, int count, boolean cursor, int foreColor, int backColor);
-}
-
-abstract class BaseTextRenderer implements TextRenderer {
- protected int[] mForePaint = {
- 0xff000000, // Black
- 0xffff0000, // Red
- 0xff00ff00, // green
- 0xffffff00, // yellow
- 0xff0000ff, // blue
- 0xffff00ff, // magenta
- 0xff00ffff, // cyan
- 0xffffffff // white -- is overridden by constructor
- };
- protected int[] mBackPaint = {
- 0xff000000, // Black -- is overridden by constructor
- 0xffcc0000, // Red
- 0xff00cc00, // green
- 0xffcccc00, // yellow
- 0xff0000cc, // blue
- 0xffff00cc, // magenta
- 0xff00cccc, // cyan
- 0xffffffff // white
- };
- protected final static int mCursorPaint = 0xff808080;
-
- public BaseTextRenderer(int forePaintColor, int backPaintColor) {
- mForePaint[7] = forePaintColor;
- mBackPaint[0] = backPaintColor;
-
- }
-}
-
-class Bitmap4x8FontRenderer extends BaseTextRenderer {
- private final static int kCharacterWidth = 4;
- private final static int kCharacterHeight = 8;
- private Bitmap mFont;
- private int mCurrentForeColor;
- private int mCurrentBackColor;
- private float[] mColorMatrix;
- private Paint mPaint;
- private static final float BYTE_SCALE = 1.0f / 255.0f;
-
- public Bitmap4x8FontRenderer(Resources resources,
- int forePaintColor, int backPaintColor) {
- super(forePaintColor, backPaintColor);
- mFont = BitmapFactory.decodeResource(resources,
- R.drawable.atari_small);
- mPaint = new Paint();
- mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
- }
-
- public int getCharacterWidth() {
- return kCharacterWidth;
- }
-
- public int getCharacterHeight() {
- return kCharacterHeight;
- }
-
- public void drawTextRun(Canvas canvas, float x, float y,
- int lineOffset, char[] text, int index, int count,
- boolean cursor, int foreColor, int backColor) {
- setColorMatrix(mForePaint[foreColor & 7],
- cursor ? mCursorPaint : mBackPaint[backColor & 7]);
- int destX = (int) x + kCharacterWidth * lineOffset;
- int destY = (int) y;
- Rect srcRect = new Rect();
- Rect destRect = new Rect();
- destRect.top = (destY - kCharacterHeight);
- destRect.bottom = destY;
- for(int i = 0; i < count; i++) {
- char c = text[i + index];
- if ((cursor || (c != 32)) && (c < 128)) {
- int cellX = c & 31;
- int cellY = (c >> 5) & 3;
- int srcX = cellX * kCharacterWidth;
- int srcY = cellY * kCharacterHeight;
- srcRect.set(srcX, srcY,
- srcX + kCharacterWidth, srcY + kCharacterHeight);
- destRect.left = destX;
- destRect.right = destX + kCharacterWidth;
- canvas.drawBitmap(mFont, srcRect, destRect, mPaint);
- }
- destX += kCharacterWidth;
- }
- }
-
- private void setColorMatrix(int foreColor, int backColor) {
- if ((foreColor != mCurrentForeColor)
- || (backColor != mCurrentBackColor)
- || (mColorMatrix == null)) {
- mCurrentForeColor = foreColor;
- mCurrentBackColor = backColor;
- if (mColorMatrix == null) {
- mColorMatrix = new float[20];
- mColorMatrix[18] = 1.0f; // Just copy Alpha
- }
- for (int component = 0; component < 3; component++) {
- int rightShift = (2 - component) << 3;
- int fore = 0xff & (foreColor >> rightShift);
- int back = 0xff & (backColor >> rightShift);
- int delta = back - fore;
- mColorMatrix[component * 6] = delta * BYTE_SCALE;
- mColorMatrix[component * 5 + 4] = fore;
- }
- mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
- }
- }
-}
-
-class PaintRenderer extends BaseTextRenderer {
- public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) {
- super(forePaintColor, backPaintColor);
- mTextPaint = new Paint();
- mTextPaint.setTypeface(Typeface.MONOSPACE);
- mTextPaint.setAntiAlias(true);
- mTextPaint.setTextSize(fontSize);
-
- mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing());
- mCharAscent = (int) Math.ceil(mTextPaint.ascent());
- mCharDescent = mCharHeight + mCharAscent;
- mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1);
- }
-
- public void drawTextRun(Canvas canvas, float x, float y, int lineOffset,
- char[] text, int index, int count,
- boolean cursor, int foreColor, int backColor) {
- if (cursor) {
- mTextPaint.setColor(mCursorPaint);
- } else {
- mTextPaint.setColor(mBackPaint[backColor & 0x7]);
- }
- float left = x + lineOffset * mCharWidth;
- canvas.drawRect(left, y + mCharAscent,
- left + count * mCharWidth, y + mCharDescent,
- mTextPaint);
- boolean bold = ( foreColor & 0x8 ) != 0;
- boolean underline = (backColor & 0x8) != 0;
- if (bold) {
- mTextPaint.setFakeBoldText(true);
- }
- if (underline) {
- mTextPaint.setUnderlineText(true);
- }
- mTextPaint.setColor(mForePaint[foreColor & 0x7]);
- canvas.drawText(text, index, count, left, y, mTextPaint);
- if (bold) {
- mTextPaint.setFakeBoldText(false);
- }
- if (underline) {
- mTextPaint.setUnderlineText(false);
- }
- }
-
- public int getCharacterHeight() {
- return mCharHeight;
- }
-
- public int getCharacterWidth() {
- return mCharWidth;
- }
-
-
- private Paint mTextPaint;
- private int mCharWidth;
- private int mCharHeight;
- private int mCharAscent;
- private int mCharDescent;
- private static final char[] EXAMPLE_CHAR = {'X'};
- }
-
-/**
- * A multi-thread-safe produce-consumer byte array.
- * Only allows one producer and one consumer.
- */
-
-class ByteQueue {
- public ByteQueue(int size) {
- mBuffer = new byte[size];
- }
-
- public int getBytesAvailable() {
- synchronized(this) {
- return mStoredBytes;
- }
- }
-
- public int read(byte[] buffer, int offset, int length)
- throws InterruptedException {
- if (length + offset > buffer.length) {
- throw
- new IllegalArgumentException("length + offset > buffer.length");
- }
- if (length < 0) {
- throw
- new IllegalArgumentException("length < 0");
-
- }
- if (length == 0) {
- return 0;
- }
- synchronized(this) {
- while (mStoredBytes == 0) {
- wait();
- }
- int totalRead = 0;
- int bufferLength = mBuffer.length;
- boolean wasFull = bufferLength == mStoredBytes;
- while (length > 0 && mStoredBytes > 0) {
- int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
- int bytesToCopy = Math.min(length, oneRun);
- System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
- mHead += bytesToCopy;
- if (mHead >= bufferLength) {
- mHead = 0;
- }
- mStoredBytes -= bytesToCopy;
- length -= bytesToCopy;
- offset += bytesToCopy;
- totalRead += bytesToCopy;
- }
- if (wasFull) {
- notify();
- }
- return totalRead;
- }
- }
-
- public void write(byte[] buffer, int offset, int length)
- throws InterruptedException {
- if (length + offset > buffer.length) {
- throw
- new IllegalArgumentException("length + offset > buffer.length");
- }
- if (length < 0) {
- throw
- new IllegalArgumentException("length < 0");
-
- }
- if (length == 0) {
- return;
- }
- synchronized(this) {
- int bufferLength = mBuffer.length;
- boolean wasEmpty = mStoredBytes == 0;
- while (length > 0) {
- while(bufferLength == mStoredBytes) {
- wait();
- }
- int tail = mHead + mStoredBytes;
- int oneRun;
- if (tail >= bufferLength) {
- tail = tail - bufferLength;
- oneRun = mHead - tail;
- } else {
- oneRun = bufferLength - tail;
- }
- int bytesToCopy = Math.min(oneRun, length);
- System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
- offset += bytesToCopy;
- mStoredBytes += bytesToCopy;
- length -= bytesToCopy;
- }
- if (wasEmpty) {
- notify();
- }
- }
- }
-
- private byte[] mBuffer;
- private int mHead;
- private int mStoredBytes;
-}
-/**
- * A view on a transcript and a terminal emulator. Displays the text of the
- * transcript and the current cursor position of the terminal emulator.
- */
-class EmulatorView extends View implements GestureDetector.OnGestureListener {
-
- private final String TAG = "EmulatorView";
- private final boolean LOG_KEY_EVENTS = Term.DEBUG && false;
-
- private Term mTerm;
-
- /**
- * We defer some initialization until we have been layed out in the view
- * hierarchy. The boolean tracks when we know what our size is.
- */
- private boolean mKnownSize;
-
- private int mVisibleWidth;
- private int mVisibleHeight;
- private Rect mVisibleRect = new Rect();
-
- /**
- * Our transcript. Contains the screen and the transcript.
- */
- private TranscriptScreen mTranscriptScreen;
-
- /**
- * Number of rows in the transcript.
- */
- private static final int TRANSCRIPT_ROWS = 10000;
-
- /**
- * Total width of each character, in pixels
- */
- private int mCharacterWidth;
-
- /**
- * Total height of each character, in pixels
- */
- private int mCharacterHeight;
-
- /**
- * Used to render text
- */
- private TextRenderer mTextRenderer;
-
- /**
- * Text size. Zero means 4 x 8 font.
- */
- private int mTextSize;
-
- private int mCursorStyle;
- private int mCursorBlink;
-
- /**
- * Foreground color.
- */
- private int mForeground;
-
- /**
- * Background color.
- */
- private int mBackground;
-
- /**
- * Used to paint the cursor
- */
- private Paint mCursorPaint;
-
- private Paint mBackgroundPaint;
-
- private boolean mUseCookedIme;
-
- /**
- * Our terminal emulator. We use this to get the current cursor position.
- */
- private TerminalEmulator mEmulator;
-
- /**
- * The number of rows of text to display.
- */
- private int mRows;
-
- /**
- * The number of columns of text to display.
- */
- private int mColumns;
-
- /**
- * The number of columns that are visible on the display.
- */
-
- private int mVisibleColumns;
-
- /**
- * The top row of text to display. Ranges from -activeTranscriptRows to 0
- */
- private int mTopRow;
-
- private int mLeftColumn;
-
- private FileDescriptor mTermFd;
- /**
- * Used to receive data from the remote process.
- */
- private FileInputStream mTermIn;
-
- private FileOutputStream mTermOut;
-
- private ByteQueue mByteQueue;
-
- /**
- * Used to temporarily hold data received from the remote process. Allocated
- * once and used permanently to minimize heap thrashing.
- */
- private byte[] mReceiveBuffer;
-
- /**
- * Our private message id, which we use to receive new input from the
- * remote process.
- */
- private static final int UPDATE = 1;
-
- private static final int SCREEN_CHECK_PERIOD = 1000;
- private static final int CURSOR_BLINK_PERIOD = 1000;
-
- private boolean mCursorVisible = true;
-
- private boolean mIsSelectingText = false;
-
-
- private float mScaledDensity;
- private static final int SELECT_TEXT_OFFSET_Y = -40;
- private int mSelXAnchor = -1;
- private int mSelYAnchor = -1;
- private int mSelX1 = -1;
- private int mSelY1 = -1;
- private int mSelX2 = -1;
- private int mSelY2 = -1;
-
- /**
- * Used to poll if the view has changed size. Wish there was a better way to do this.
- */
- private Runnable mCheckSize = new Runnable() {
-
- public void run() {
- updateSize(false);
- mHandler.postDelayed(this, SCREEN_CHECK_PERIOD);
- }
- };
-
- private Runnable mBlinkCursor = new Runnable() {
- public void run() {
- if (mCursorBlink != 0) {
- mCursorVisible = ! mCursorVisible;
- mHandler.postDelayed(this, CURSOR_BLINK_PERIOD);
- } else {
- mCursorVisible = true;
- }
- // Perhaps just invalidate the character with the cursor.
- invalidate();
- }
- };
-
- /**
- * Thread that polls for input from the remote process
- */
-
- private Thread mPollingThread;
-
- private GestureDetector mGestureDetector;
- private float mScrollRemainder;
- private TermKeyListener mKeyListener;
-
- /**
- * Our message handler class. Implements a periodic callback.
- */
- private final Handler mHandler = new Handler() {
- /**
- * Handle the callback message. Call our enclosing class's update
- * method.
- *
- * @param msg The callback message.
- */
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == UPDATE) {
- update();
- }
- }
- };
-
- public EmulatorView(Context context) {
- super(context);
- commonConstructor();
- }
-
- public void setScaledDensity(float scaledDensity) {
- mScaledDensity = scaledDensity;
- }
-
- public void onResume() {
- updateSize(false);
- mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD);
- if (mCursorBlink != 0) {
- mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
- }
- }
-
- public void onPause() {
- mHandler.removeCallbacks(mCheckSize);
- if (mCursorBlink != 0) {
- mHandler.removeCallbacks(mBlinkCursor);
- }
- }
-
- public void register(Term term, TermKeyListener listener) {
- mTerm = term;
- mKeyListener = listener;
- }
-
- public void setColors(int foreground, int background) {
- mForeground = foreground;
- mBackground = background;
- updateText();
- }
-
- public String getTranscriptText() {
- return mEmulator.getTranscriptText();
- }
-
- public void resetTerminal() {
- mEmulator.reset();
- invalidate();
- }
-
- @Override
- public boolean onCheckIsTextEditor() {
- return true;
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- outAttrs.inputType = mUseCookedIme ?
- EditorInfo.TYPE_CLASS_TEXT :
- EditorInfo.TYPE_NULL;
- return new BaseInputConnection(this, false) {
-
- @Override
- public boolean commitText(CharSequence text, int newCursorPosition) {
- sendText(text);
- return true;
- }
-
- @Override
- public boolean performEditorAction(int actionCode) {
- if(actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
- // The "return" key has been pressed on the IME.
- sendText("\n");
- return true;
- }
- return false;
- }
-
- @Override
- public boolean sendKeyEvent(KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- // Some keys are sent here rather than to commitText.
- // In particular, del and the digit keys are sent here.
- // (And I have reports that the HTC Magic also sends Return here.)
- // As a bit of defensive programming, handle every
- // key with an ASCII meaning.
- int keyCode = event.getKeyCode();
- if (keyCode >= 0 && keyCode < KEYCODE_CHARS.length()) {
- char c = KEYCODE_CHARS.charAt(keyCode);
- if (c > 0) {
- sendChar(c);
- } else {
- // Handle IME arrow key events
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP: // Up Arrow
- case KeyEvent.KEYCODE_DPAD_DOWN: // Down Arrow
- case KeyEvent.KEYCODE_DPAD_LEFT: // Left Arrow
- case KeyEvent.KEYCODE_DPAD_RIGHT: // Right Arrow
- super.sendKeyEvent(event);
- break;
- default:
- break;
- } // switch (keyCode)
- }
- }
- }
- return true;
- }
-
- private final String KEYCODE_CHARS =
- "\000\000\000\000\000\000\000" + "0123456789*#"
- + "\000\000\000\000\000\000\000\000\000\000"
- + "abcdefghijklmnopqrstuvwxyz,."
- + "\000\000\000\000"
- + "\011 " // tab, space
- + "\000\000\000" // sym .. envelope
- + "\015\177" // enter, del
- + "`-=[]\\;'/@"
- + "\000\000"
- + "\000+";
-
- @Override
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
- return true;
- }
-
- @Override
- public boolean setSelection(int start, int end) {
- return true;
- }
-
- @Override
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- if (leftLength > 0) {
- for (int i = 0; i < leftLength; i++) {
- sendKeyEvent(
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
- }
- } else if ((leftLength == 0) && (rightLength == 0)) {
- // Delete key held down / repeating
- sendKeyEvent(
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
- }
- // TODO: handle forward deletes.
- return true;
- }
-
- private void sendChar(int c) {
- try {
- mapAndSend(c);
- } catch (IOException ex) {
-
- }
- }
- private void sendText(CharSequence text) {
- int n = text.length();
- try {
- for(int i = 0; i < n; i++) {
- char c = text.charAt(i);
- mapAndSend(c);
- }
- } catch (IOException e) {
- }
- }
-
- private void mapAndSend(int c) throws IOException {
- mTermOut.write(
- mKeyListener.mapControlChar(c));
- }
- };
- }
-
- public boolean getKeypadApplicationMode() {
- return mEmulator.getKeypadApplicationMode();
- }
-
- public EmulatorView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public EmulatorView(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
- // TypedArray a =
- // context.obtainStyledAttributes(android.R.styleable.View);
- // initializeScrollbars(a);
- // a.recycle();
- commonConstructor();
- }
-
- private void commonConstructor() {
- mTextRenderer = null;
- mCursorPaint = new Paint();
- mCursorPaint.setARGB(255,128,128,128);
- mBackgroundPaint = new Paint();
- mTopRow = 0;
- mLeftColumn = 0;
- mGestureDetector = new GestureDetector(this);
- // mGestureDetector.setIsLongpressEnabled(false);
- setVerticalScrollBarEnabled(true);
- }
-
- @Override
- protected int computeVerticalScrollRange() {
- return mTranscriptScreen.getActiveRows();
- }
-
- @Override
- protected int computeVerticalScrollExtent() {
- return mRows;
- }
-
- @Override
- protected int computeVerticalScrollOffset() {
- return mTranscriptScreen.getActiveRows() + mTopRow - mRows;
- }
-
- /**
- * Call this to initialize the view.
- *
- * @param termFd the file descriptor
- * @param termOut the output stream for the pseudo-teletype
- */
- public void initialize(FileDescriptor termFd, FileOutputStream termOut) {
- mTermOut = termOut;
- mTermFd = termFd;
- mTextSize = 10;
- mForeground = Term.WHITE;
- mBackground = Term.BLACK;
- updateText();
- mTermIn = new FileInputStream(mTermFd);
- mReceiveBuffer = new byte[4 * 1024];
- mByteQueue = new ByteQueue(4 * 1024);
- }
-
- /**
- * Accept a sequence of bytes (typically from the pseudo-tty) and process
- * them.
- *
- * @param buffer a byte array containing bytes to be processed
- * @param base the index of the first byte in the buffer to process
- * @param length the number of bytes to process
- */
- public void append(byte[] buffer, int base, int length) {
- mEmulator.append(buffer, base, length);
- if ( mIsSelectingText ) {
- int rowShift = mEmulator.getScrollCounter();
- mSelY1 -= rowShift;
- mSelY2 -= rowShift;
- mSelYAnchor -= rowShift;
- }
- mEmulator.clearScrollCounter();
- ensureCursorVisible();
- invalidate();
- }
-
- /**
- * Page the terminal view (scroll it up or down by delta screenfulls.)
- *
- * @param delta the number of screens to scroll. Positive means scroll down,
- * negative means scroll up.
- */
- public void page(int delta) {
- mTopRow =
- Math.min(0, Math.max(-(mTranscriptScreen
- .getActiveTranscriptRows()), mTopRow + mRows * delta));
- invalidate();
- }
-
- /**
- * Page the terminal view horizontally.
- *
- * @param deltaColumns the number of columns to scroll. Positive scrolls to
- * the right.
- */
- public void pageHorizontal(int deltaColumns) {
- mLeftColumn =
- Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns
- - mVisibleColumns));
- invalidate();
- }
-
- /**
- * Sets the text size, which in turn sets the number of rows and columns
- *
- * @param fontSize the new font size, in pixels.
- */
- public void setTextSize(int fontSize) {
- mTextSize = fontSize;
- updateText();
- }
-
- public void setCursorStyle(int style, int blink) {
- mCursorStyle = style;
- if (blink != 0 && mCursorBlink == 0) {
- mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
- } else if (blink == 0 && mCursorBlink != 0) {
- mHandler.removeCallbacks(mBlinkCursor);
- }
- mCursorBlink = blink;
- }
-
- public void setUseCookedIME(boolean useRawIME) {
- mUseCookedIme = useRawIME;
- }
-
- // Begin GestureDetector.OnGestureListener methods
-
- public boolean onSingleTapUp(MotionEvent e) {
- return true;
- }
-
- public void onLongPress(MotionEvent e) {
- showContextMenu();
- }
-
- public boolean onScroll(MotionEvent e1, MotionEvent e2,
- float distanceX, float distanceY) {
- distanceY += mScrollRemainder;
- int deltaRows = (int) (distanceY / mCharacterHeight);
- mScrollRemainder = distanceY - deltaRows * mCharacterHeight;
- mTopRow =
- Math.min(0, Math.max(-(mTranscriptScreen
- .getActiveTranscriptRows()), mTopRow + deltaRows));
- invalidate();
-
- return true;
- }
-
- public void onSingleTapConfirmed(MotionEvent e) {
- }
-
- public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) {
- // Scroll to bottom
- mTopRow = 0;
- invalidate();
- return true;
- }
-
- public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) {
- // Scroll to top
- mTopRow = -mTranscriptScreen.getActiveTranscriptRows();
- invalidate();
- return true;
- }
-
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- // TODO: add animation man's (non animated) fling
- mScrollRemainder = 0.0f;
- onScroll(e1, e2, 2 * velocityX, -2 * velocityY);
- return true;
- }
-
- public void onShowPress(MotionEvent e) {
- }
-
- public boolean onDown(MotionEvent e) {
- mScrollRemainder = 0.0f;
- return true;
- }
-
- // End GestureDetector.OnGestureListener methods
-
- @Override public boolean onTouchEvent(MotionEvent ev) {
- if (mIsSelectingText) {
- return onTouchEventWhileSelectingText(ev);
- } else {
- return mGestureDetector.onTouchEvent(ev);
- }
- }
-
- private boolean onTouchEventWhileSelectingText(MotionEvent ev) {
- int action = ev.getAction();
- int cx = (int)(ev.getX() / mCharacterWidth);
- int cy = Math.max(0,
- (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity)
- / mCharacterHeight) + mTopRow);
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mSelXAnchor = cx;
- mSelYAnchor = cy;
- mSelX1 = cx;
- mSelY1 = cy;
- mSelX2 = mSelX1;
- mSelY2 = mSelY1;
- break;
- case MotionEvent.ACTION_MOVE:
- case MotionEvent.ACTION_UP:
- int minx = Math.min(mSelXAnchor, cx);
- int maxx = Math.max(mSelXAnchor, cx);
- int miny = Math.min(mSelYAnchor, cy);
- int maxy = Math.max(mSelYAnchor, cy);
- mSelX1 = minx;
- mSelY1 = miny;
- mSelX2 = maxx;
- mSelY2 = maxy;
- if (action == MotionEvent.ACTION_UP) {
- ClipboardManager clip = (ClipboardManager)
- getContext().getApplicationContext()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- clip.setText(getSelectedText().trim());
- toggleSelectingText();
- }
- invalidate();
- break;
- default:
- toggleSelectingText();
- invalidate();
- break;
- }
- return true;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (LOG_KEY_EVENTS) {
- Log.w(TAG, "onKeyDown " + keyCode);
- }
- if (handleControlKey(keyCode, true)) {
- return true;
- } else if (isSystemKey(keyCode, event)) {
- // Don't intercept the system keys
- return super.onKeyDown(keyCode, event);
- }
-
- // Translate the keyCode into an ASCII character.
-
- try {
- mKeyListener.keyDown(keyCode, event, mTermOut,
- getKeypadApplicationMode());
- } catch (IOException e) {
- // Ignore I/O exceptions
- }
- return true;
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (LOG_KEY_EVENTS) {
- Log.w(TAG, "onKeyUp " + keyCode);
- }
- if (handleControlKey(keyCode, false)) {
- return true;
- } else if (isSystemKey(keyCode, event)) {
- // Don't intercept the system keys
- return super.onKeyUp(keyCode, event);
- }
-
- mKeyListener.keyUp(keyCode);
- return true;
- }
-
-
- private boolean handleControlKey(int keyCode, boolean down) {
- if (keyCode == mTerm.getControlKeyCode()) {
- if (LOG_KEY_EVENTS) {
- Log.w(TAG, "handleControlKey " + keyCode);
- }
- mKeyListener.handleControlKey(down);
- return true;
- }
- return false;
- }
-
- private boolean isSystemKey(int keyCode, KeyEvent event) {
- return event.isSystem();
- }
-
- private void updateText() {
- if (mTextSize > 0) {
- mTextRenderer = new PaintRenderer(mTextSize, mForeground,
- mBackground);
- }
- else {
- mTextRenderer = new Bitmap4x8FontRenderer(getResources(),
- mForeground, mBackground);
- }
- mBackgroundPaint.setColor(mBackground);
- mCharacterWidth = mTextRenderer.getCharacterWidth();
- mCharacterHeight = mTextRenderer.getCharacterHeight();
-
- updateSize(true);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- boolean oldKnownSize = mKnownSize;
- if (!mKnownSize) {
- mKnownSize = true;
- }
- updateSize(false);
- if (!oldKnownSize) {
- // Set up a thread to read input from the
- // pseudo-teletype:
-
- mPollingThread = new Thread(new Runnable() {
-
- public void run() {
- try {
- while(true) {
- int read = mTermIn.read(mBuffer);
- mByteQueue.write(mBuffer, 0, read);
- mHandler.sendMessage(
- mHandler.obtainMessage(UPDATE));
- }
- } catch (IOException e) {
- } catch (InterruptedException e) {
- }
- }
- private byte[] mBuffer = new byte[4096];
- });
- mPollingThread.setName("Input reader");
- mPollingThread.start();
- }
- }
-
- private void updateSize(int w, int h) {
- mColumns = Math.max(1, w / mCharacterWidth);
- mRows = Math.max(1, h / mCharacterHeight);
- mVisibleColumns = mVisibleWidth / mCharacterWidth;
-
- // Inform the attached pty of our new size:
- Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h);
-
-
- if (mTranscriptScreen != null) {
- mEmulator.updateSize(mColumns, mRows);
- } else {
- mTranscriptScreen =
- new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7);
- mEmulator =
- new TerminalEmulator(mTranscriptScreen, mColumns, mRows,
- mTermOut);
- }
-
- // Reset our paging:
- mTopRow = 0;
- mLeftColumn = 0;
-
- invalidate();
- }
-
- void updateSize(boolean force) {
- if (mKnownSize) {
- getWindowVisibleDisplayFrame(mVisibleRect);
- int w = mVisibleRect.width();
- int h = mVisibleRect.height();
- // Log.w("Term", "(" + w + ", " + h + ")");
- if (force || w != mVisibleWidth || h != mVisibleHeight) {
- mVisibleWidth = w;
- mVisibleHeight = h;
- updateSize(mVisibleWidth, mVisibleHeight);
- }
- }
- }
-
- /**
- * Look for new input from the ptty, send it to the terminal emulator.
- */
- private void update() {
- int bytesAvailable = mByteQueue.getBytesAvailable();
- int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
- try {
- int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
- append(mReceiveBuffer, 0, bytesRead);
- } catch (InterruptedException e) {
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- updateSize(false);
- int w = getWidth();
- int h = getHeight();
- canvas.drawRect(0, 0, w, h, mBackgroundPaint);
- float x = -mLeftColumn * mCharacterWidth;
- float y = mCharacterHeight;
- int endLine = mTopRow + mRows;
- int cx = mEmulator.getCursorCol();
- int cy = mEmulator.getCursorRow();
- for (int i = mTopRow; i < endLine; i++) {
- int cursorX = -1;
- if (i == cy && mCursorVisible) {
- cursorX = cx;
- }
- int selx1 = -1;
- int selx2 = -1;
- if ( i >= mSelY1 && i <= mSelY2 ) {
- if ( i == mSelY1 ) {
- selx1 = mSelX1;
- }
- if ( i == mSelY2 ) {
- selx2 = mSelX2;
- } else {
- selx2 = mColumns;
- }
- }
- mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2);
- y += mCharacterHeight;
- }
- }
-
- private void ensureCursorVisible() {
- mTopRow = 0;
- if (mVisibleColumns > 0) {
- int cx = mEmulator.getCursorCol();
- int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn;
- if (visibleCursorX < 0) {
- mLeftColumn = cx;
- } else if (visibleCursorX >= mVisibleColumns) {
- mLeftColumn = (cx - mVisibleColumns) + 1;
- }
- }
- }
-
- public void toggleSelectingText() {
- mIsSelectingText = ! mIsSelectingText;
- setVerticalScrollBarEnabled( ! mIsSelectingText );
- if ( ! mIsSelectingText ) {
- mSelX1 = -1;
- mSelY1 = -1;
- mSelX2 = -1;
- mSelY2 = -1;
- }
- }
-
- public boolean getSelectingText() {
- return mIsSelectingText;
- }
-
- public String getSelectedText() {
- return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2);
- }
-}
-
-
-/**
- * An ASCII key listener. Supports control characters and escape. Keeps track of
- * the current state of the alt, shift, and control keys.
- */
-class TermKeyListener {
- /**
- * Android key codes that are defined in the Android 2.3 API.
- * We want to recognize these codes, because they will be sent to our
- * app when we run on Android 2.3 systems.
- * But we don't want to accidentally use 2.3-specific APIs.
- * So we compile against the Android 1.6 APIs, and have a copy of the codes here.
- */
-
- /** Key code constant: Unknown key code. */
- public static final int KEYCODE_UNKNOWN = 0;
- /** Key code constant: Soft Left key.
- * Usually situated below the display on phones and used as a multi-function
- * feature key for selecting a software defined function shown on the bottom left
- * of the display. */
- public static final int KEYCODE_SOFT_LEFT = 1;
- /** Key code constant: Soft Right key.
- * Usually situated below the display on phones and used as a multi-function
- * feature key for selecting a software defined function shown on the bottom right
- * of the display. */
- public static final int KEYCODE_SOFT_RIGHT = 2;
- /** Key code constant: Home key.
- * This key is handled by the framework and is never delivered to applications. */
- public static final int KEYCODE_HOME = 3;
- /** Key code constant: Back key. */
- public static final int KEYCODE_BACK = 4;
- /** Key code constant: Call key. */
- public static final int KEYCODE_CALL = 5;
- /** Key code constant: End Call key. */
- public static final int KEYCODE_ENDCALL = 6;
- /** Key code constant: '0' key. */
- public static final int KEYCODE_0 = 7;
- /** Key code constant: '1' key. */
- public static final int KEYCODE_1 = 8;
- /** Key code constant: '2' key. */
- public static final int KEYCODE_2 = 9;
- /** Key code constant: '3' key. */
- public static final int KEYCODE_3 = 10;
- /** Key code constant: '4' key. */
- public static final int KEYCODE_4 = 11;
- /** Key code constant: '5' key. */
- public static final int KEYCODE_5 = 12;
- /** Key code constant: '6' key. */
- public static final int KEYCODE_6 = 13;
- /** Key code constant: '7' key. */
- public static final int KEYCODE_7 = 14;
- /** Key code constant: '8' key. */
- public static final int KEYCODE_8 = 15;
- /** Key code constant: '9' key. */
- public static final int KEYCODE_9 = 16;
- /** Key code constant: '*' key. */
- public static final int KEYCODE_STAR = 17;
- /** Key code constant: '#' key. */
- public static final int KEYCODE_POUND = 18;
- /** Key code constant: Directional Pad Up key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_UP = 19;
- /** Key code constant: Directional Pad Down key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_DOWN = 20;
- /** Key code constant: Directional Pad Left key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_LEFT = 21;
- /** Key code constant: Directional Pad Right key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_RIGHT = 22;
- /** Key code constant: Directional Pad Center key.
- * May also be synthesized from trackball motions. */
- public static final int KEYCODE_DPAD_CENTER = 23;
- /** Key code constant: Volume Up key.
- * Adjusts the speaker volume up. */
- public static final int KEYCODE_VOLUME_UP = 24;
- /** Key code constant: Volume Down key.
- * Adjusts the speaker volume down. */
- public static final int KEYCODE_VOLUME_DOWN = 25;
- /** Key code constant: Power key. */
- public static final int KEYCODE_POWER = 26;
- /** Key code constant: Camera key.
- * Used to launch a camera application or take pictures. */
- public static final int KEYCODE_CAMERA = 27;
- /** Key code constant: Clear key. */
- public static final int KEYCODE_CLEAR = 28;
- /** Key code constant: 'A' key. */
- public static final int KEYCODE_A = 29;
- /** Key code constant: 'B' key. */
- public static final int KEYCODE_B = 30;
- /** Key code constant: 'C' key. */
- public static final int KEYCODE_C = 31;
- /** Key code constant: 'D' key. */
- public static final int KEYCODE_D = 32;
- /** Key code constant: 'E' key. */
- public static final int KEYCODE_E = 33;
- /** Key code constant: 'F' key. */
- public static final int KEYCODE_F = 34;
- /** Key code constant: 'G' key. */
- public static final int KEYCODE_G = 35;
- /** Key code constant: 'H' key. */
- public static final int KEYCODE_H = 36;
- /** Key code constant: 'I' key. */
- public static final int KEYCODE_I = 37;
- /** Key code constant: 'J' key. */
- public static final int KEYCODE_J = 38;
- /** Key code constant: 'K' key. */
- public static final int KEYCODE_K = 39;
- /** Key code constant: 'L' key. */
- public static final int KEYCODE_L = 40;
- /** Key code constant: 'M' key. */
- public static final int KEYCODE_M = 41;
- /** Key code constant: 'N' key. */
- public static final int KEYCODE_N = 42;
- /** Key code constant: 'O' key. */
- public static final int KEYCODE_O = 43;
- /** Key code constant: 'P' key. */
- public static final int KEYCODE_P = 44;
- /** Key code constant: 'Q' key. */
- public static final int KEYCODE_Q = 45;
- /** Key code constant: 'R' key. */
- public static final int KEYCODE_R = 46;
- /** Key code constant: 'S' key. */
- public static final int KEYCODE_S = 47;
- /** Key code constant: 'T' key. */
- public static final int KEYCODE_T = 48;
- /** Key code constant: 'U' key. */
- public static final int KEYCODE_U = 49;
- /** Key code constant: 'V' key. */
- public static final int KEYCODE_V = 50;
- /** Key code constant: 'W' key. */
- public static final int KEYCODE_W = 51;
- /** Key code constant: 'X' key. */
- public static final int KEYCODE_X = 52;
- /** Key code constant: 'Y' key. */
- public static final int KEYCODE_Y = 53;
- /** Key code constant: 'Z' key. */
- public static final int KEYCODE_Z = 54;
- /** Key code constant: ',' key. */
- public static final int KEYCODE_COMMA = 55;
- /** Key code constant: '.' key. */
- public static final int KEYCODE_PERIOD = 56;
- /** Key code constant: Left Alt modifier key. */
- public static final int KEYCODE_ALT_LEFT = 57;
- /** Key code constant: Right Alt modifier key. */
- public static final int KEYCODE_ALT_RIGHT = 58;
- /** Key code constant: Left Shift modifier key. */
- public static final int KEYCODE_SHIFT_LEFT = 59;
- /** Key code constant: Right Shift modifier key. */
- public static final int KEYCODE_SHIFT_RIGHT = 60;
- /** Key code constant: Tab key. */
- public static final int KEYCODE_TAB = 61;
- /** Key code constant: Space key. */
- public static final int KEYCODE_SPACE = 62;
- /** Key code constant: Symbol modifier key.
- * Used to enter alternate symbols. */
- public static final int KEYCODE_SYM = 63;
- /** Key code constant: Explorer special function key.
- * Used to launch a browser application. */
- public static final int KEYCODE_EXPLORER = 64;
- /** Key code constant: Envelope special function key.
- * Used to launch a mail application. */
- public static final int KEYCODE_ENVELOPE = 65;
- /** Key code constant: Enter key. */
- public static final int KEYCODE_ENTER = 66;
- /** Key code constant: Backspace key.
- * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
- public static final int KEYCODE_DEL = 67;
- /** Key code constant: '`' (backtick) key. */
- public static final int KEYCODE_GRAVE = 68;
- /** Key code constant: '-'. */
- public static final int KEYCODE_MINUS = 69;
- /** Key code constant: '=' key. */
- public static final int KEYCODE_EQUALS = 70;
- /** Key code constant: '[' key. */
- public static final int KEYCODE_LEFT_BRACKET = 71;
- /** Key code constant: ']' key. */
- public static final int KEYCODE_RIGHT_BRACKET = 72;
- /** Key code constant: '\' key. */
- public static final int KEYCODE_BACKSLASH = 73;
- /** Key code constant: ';' key. */
- public static final int KEYCODE_SEMICOLON = 74;
- /** Key code constant: ''' (apostrophe) key. */
- public static final int KEYCODE_APOSTROPHE = 75;
- /** Key code constant: '/' key. */
- public static final int KEYCODE_SLASH = 76;
- /** Key code constant: '@' key. */
- public static final int KEYCODE_AT = 77;
- /** Key code constant: Number modifier key.
- * Used to enter numeric symbols.
- * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
- * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
- public static final int KEYCODE_NUM = 78;
- /** Key code constant: Headset Hook key.
- * Used to hang up calls and stop media. */
- public static final int KEYCODE_HEADSETHOOK = 79;
- /** Key code constant: Camera Focus key.
- * Used to focus the camera. */
- public static final int KEYCODE_FOCUS = 80; // *Camera* focus
- /** Key code constant: '+' key. */
- public static final int KEYCODE_PLUS = 81;
- /** Key code constant: Menu key. */
- public static final int KEYCODE_MENU = 82;
- /** Key code constant: Notification key. */
- public static final int KEYCODE_NOTIFICATION = 83;
- /** Key code constant: Search key. */
- public static final int KEYCODE_SEARCH = 84;
- /** Key code constant: Play/Pause media key. */
- public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
- /** Key code constant: Stop media key. */
- public static final int KEYCODE_MEDIA_STOP = 86;
- /** Key code constant: Play Next media key. */
- public static final int KEYCODE_MEDIA_NEXT = 87;
- /** Key code constant: Play Previous media key. */
- public static final int KEYCODE_MEDIA_PREVIOUS = 88;
- /** Key code constant: Rewind media key. */
- public static final int KEYCODE_MEDIA_REWIND = 89;
- /** Key code constant: Fast Forward media key. */
- public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
- /** Key code constant: Mute key.
- * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
- public static final int KEYCODE_MUTE = 91;
- /** Key code constant: Page Up key. */
- public static final int KEYCODE_PAGE_UP = 92;
- /** Key code constant: Page Down key. */
- public static final int KEYCODE_PAGE_DOWN = 93;
- /** Key code constant: Picture Symbols modifier key.
- * Used to switch symbol sets (Emoji, Kao-moji). */
- public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji)
- /** Key code constant: Switch Charset modifier key.
- * Used to switch character sets (Kanji, Katakana). */
- public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
- /** Key code constant: A Button key.
- * On a game controller, the A button should be either the button labeled A
- * or the first button on the upper row of controller buttons. */
- public static final int KEYCODE_BUTTON_A = 96;
- /** Key code constant: B Button key.
- * On a game controller, the B button should be either the button labeled B
- * or the second button on the upper row of controller buttons. */
- public static final int KEYCODE_BUTTON_B = 97;
- /** Key code constant: C Button key.
- * On a game controller, the C button should be either the button labeled C
- * or the third button on the upper row of controller buttons. */
- public static final int KEYCODE_BUTTON_C = 98;
- /** Key code constant: X Button key.
- * On a game controller, the X button should be either the button labeled X
- * or the first button on the lower row of controller buttons. */
- public static final int KEYCODE_BUTTON_X = 99;
- /** Key code constant: Y Button key.
- * On a game controller, the Y button should be either the button labeled Y
- * or the second button on the lower row of controller buttons. */
- public static final int KEYCODE_BUTTON_Y = 100;
- /** Key code constant: Z Button key.
- * On a game controller, the Z button should be either the button labeled Z
- * or the third button on the lower row of controller buttons. */
- public static final int KEYCODE_BUTTON_Z = 101;
- /** Key code constant: L1 Button key.
- * On a game controller, the L1 button should be either the button labeled L1 (or L)
- * or the top left trigger button. */
- public static final int KEYCODE_BUTTON_L1 = 102;
- /** Key code constant: R1 Button key.
- * On a game controller, the R1 button should be either the button labeled R1 (or R)
- * or the top right trigger button. */
- public static final int KEYCODE_BUTTON_R1 = 103;
- /** Key code constant: L2 Button key.
- * On a game controller, the L2 button should be either the button labeled L2
- * or the bottom left trigger button. */
- public static final int KEYCODE_BUTTON_L2 = 104;
- /** Key code constant: R2 Button key.
- * On a game controller, the R2 button should be either the button labeled R2
- * or the bottom right trigger button. */
- public static final int KEYCODE_BUTTON_R2 = 105;
- /** Key code constant: Left Thumb Button key.
- * On a game controller, the left thumb button indicates that the left (or only)
- * joystick is pressed. */
- public static final int KEYCODE_BUTTON_THUMBL = 106;
- /** Key code constant: Right Thumb Button key.
- * On a game controller, the right thumb button indicates that the right
- * joystick is pressed. */
- public static final int KEYCODE_BUTTON_THUMBR = 107;
- /** Key code constant: Start Button key.
- * On a game controller, the button labeled Start. */
- public static final int KEYCODE_BUTTON_START = 108;
- /** Key code constant: Select Button key.
- * On a game controller, the button labeled Select. */
- public static final int KEYCODE_BUTTON_SELECT = 109;
- /** Key code constant: Mode Button key.
- * On a game controller, the button labeled Mode. */
- public static final int KEYCODE_BUTTON_MODE = 110;
- /** Key code constant: Escape key. */
- public static final int KEYCODE_ESCAPE = 111;
- /** Key code constant: Forward Delete key.
- * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
- public static final int KEYCODE_FORWARD_DEL = 112;
- /** Key code constant: Left Control modifier key. */
- public static final int KEYCODE_CTRL_LEFT = 113;
- /** Key code constant: Right Control modifier key. */
- public static final int KEYCODE_CTRL_RIGHT = 114;
- /** Key code constant: Caps Lock modifier key. */
- public static final int KEYCODE_CAPS_LOCK = 115;
- /** Key code constant: Scroll Lock key. */
- public static final int KEYCODE_SCROLL_LOCK = 116;
- /** Key code constant: Left Meta modifier key. */
- public static final int KEYCODE_META_LEFT = 117;
- /** Key code constant: Right Meta modifier key. */
- public static final int KEYCODE_META_RIGHT = 118;
- /** Key code constant: Function modifier key. */
- public static final int KEYCODE_FUNCTION = 119;
- /** Key code constant: System Request / Print Screen key. */
- public static final int KEYCODE_SYSRQ = 120;
- /** Key code constant: Break / Pause key. */
- public static final int KEYCODE_BREAK = 121;
- /** Key code constant: Home Movement key.
- * Used for scrolling or moving the cursor around to the start of a line
- * or to the top of a list. */
- public static final int KEYCODE_MOVE_HOME = 122;
- /** Key code constant: End Movement key.
- * Used for scrolling or moving the cursor around to the end of a line
- * or to the bottom of a list. */
- public static final int KEYCODE_MOVE_END = 123;
- /** Key code constant: Insert key.
- * Toggles insert / overwrite edit mode. */
- public static final int KEYCODE_INSERT = 124;
- /** Key code constant: Forward key.
- * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */
- public static final int KEYCODE_FORWARD = 125;
- /** Key code constant: Play media key. */
- public static final int KEYCODE_MEDIA_PLAY = 126;
- /** Key code constant: Pause media key. */
- public static final int KEYCODE_MEDIA_PAUSE = 127;
- /** Key code constant: Close media key.
- * May be used to close a CD tray, for example. */
- public static final int KEYCODE_MEDIA_CLOSE = 128;
- /** Key code constant: Eject media key.
- * May be used to eject a CD tray, for example. */
- public static final int KEYCODE_MEDIA_EJECT = 129;
- /** Key code constant: Record media key. */
- public static final int KEYCODE_MEDIA_RECORD = 130;
- /** Key code constant: F1 key. */
- public static final int KEYCODE_F1 = 131;
- /** Key code constant: F2 key. */
- public static final int KEYCODE_F2 = 132;
- /** Key code constant: F3 key. */
- public static final int KEYCODE_F3 = 133;
- /** Key code constant: F4 key. */
- public static final int KEYCODE_F4 = 134;
- /** Key code constant: F5 key. */
- public static final int KEYCODE_F5 = 135;
- /** Key code constant: F6 key. */
- public static final int KEYCODE_F6 = 136;
- /** Key code constant: F7 key. */
- public static final int KEYCODE_F7 = 137;
- /** Key code constant: F8 key. */
- public static final int KEYCODE_F8 = 138;
- /** Key code constant: F9 key. */
- public static final int KEYCODE_F9 = 139;
- /** Key code constant: F10 key. */
- public static final int KEYCODE_F10 = 140;
- /** Key code constant: F11 key. */
- public static final int KEYCODE_F11 = 141;
- /** Key code constant: F12 key. */
- public static final int KEYCODE_F12 = 142;
- /** Key code constant: Num Lock modifier key.
- * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
- * This key generally modifies the behavior of other keys on the numeric keypad. */
- public static final int KEYCODE_NUM_LOCK = 143;
- /** Key code constant: Numeric keypad '0' key. */
- public static final int KEYCODE_NUMPAD_0 = 144;
- /** Key code constant: Numeric keypad '1' key. */
- public static final int KEYCODE_NUMPAD_1 = 145;
- /** Key code constant: Numeric keypad '2' key. */
- public static final int KEYCODE_NUMPAD_2 = 146;
- /** Key code constant: Numeric keypad '3' key. */
- public static final int KEYCODE_NUMPAD_3 = 147;
- /** Key code constant: Numeric keypad '4' key. */
- public static final int KEYCODE_NUMPAD_4 = 148;
- /** Key code constant: Numeric keypad '5' key. */
- public static final int KEYCODE_NUMPAD_5 = 149;
- /** Key code constant: Numeric keypad '6' key. */
- public static final int KEYCODE_NUMPAD_6 = 150;
- /** Key code constant: Numeric keypad '7' key. */
- public static final int KEYCODE_NUMPAD_7 = 151;
- /** Key code constant: Numeric keypad '8' key. */
- public static final int KEYCODE_NUMPAD_8 = 152;
- /** Key code constant: Numeric keypad '9' key. */
- public static final int KEYCODE_NUMPAD_9 = 153;
- /** Key code constant: Numeric keypad '/' key (for division). */
- public static final int KEYCODE_NUMPAD_DIVIDE = 154;
- /** Key code constant: Numeric keypad '*' key (for multiplication). */
- public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
- /** Key code constant: Numeric keypad '-' key (for subtraction). */
- public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
- /** Key code constant: Numeric keypad '+' key (for addition). */
- public static final int KEYCODE_NUMPAD_ADD = 157;
- /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
- public static final int KEYCODE_NUMPAD_DOT = 158;
- /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
- public static final int KEYCODE_NUMPAD_COMMA = 159;
- /** Key code constant: Numeric keypad Enter key. */
- public static final int KEYCODE_NUMPAD_ENTER = 160;
- /** Key code constant: Numeric keypad '=' key. */
- public static final int KEYCODE_NUMPAD_EQUALS = 161;
- /** Key code constant: Numeric keypad '(' key. */
- public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
- /** Key code constant: Numeric keypad ')' key. */
- public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
- /** Key code constant: Volume Mute key.
- * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
- * This key should normally be implemented as a toggle such that the first press
- * mutes the speaker and the second press restores the original volume. */
- public static final int KEYCODE_VOLUME_MUTE = 164;
- /** Key code constant: Info key.
- * Common on TV remotes to show additional information related to what is
- * currently being viewed. */
- public static final int KEYCODE_INFO = 165;
- /** Key code constant: Channel up key.
- * On TV remotes, increments the television channel. */
- public static final int KEYCODE_CHANNEL_UP = 166;
- /** Key code constant: Channel down key.
- * On TV remotes, decrements the television channel. */
- public static final int KEYCODE_CHANNEL_DOWN = 167;
- /** Key code constant: Zoom in key. */
- public static final int KEYCODE_ZOOM_IN = 168;
- /** Key code constant: Zoom out key. */
- public static final int KEYCODE_ZOOM_OUT = 169;
- /** Key code constant: TV key.
- * On TV remotes, switches to viewing live TV. */
- public static final int KEYCODE_TV = 170;
- /** Key code constant: Window key.
- * On TV remotes, toggles picture-in-picture mode or other windowing functions. */
- public static final int KEYCODE_WINDOW = 171;
- /** Key code constant: Guide key.
- * On TV remotes, shows a programming guide. */
- public static final int KEYCODE_GUIDE = 172;
- /** Key code constant: DVR key.
- * On some TV remotes, switches to a DVR mode for recorded shows. */
- public static final int KEYCODE_DVR = 173;
- /** Key code constant: Bookmark key.
- * On some TV remotes, bookmarks content or web pages. */
- public static final int KEYCODE_BOOKMARK = 174;
- /** Key code constant: Toggle captions key.
- * Switches the mode for closed-captioning text, for example during television shows. */
- public static final int KEYCODE_CAPTIONS = 175;
- /** Key code constant: Settings key.
- * Starts the system settings activity. */
- public static final int KEYCODE_SETTINGS = 176;
- /** Key code constant: TV power key.
- * On TV remotes, toggles the power on a television screen. */
- public static final int KEYCODE_TV_POWER = 177;
- /** Key code constant: TV input key.
- * On TV remotes, switches the input on a television screen. */
- public static final int KEYCODE_TV_INPUT = 178;
- /** Key code constant: Set-top-box power key.
- * On TV remotes, toggles the power on an external Set-top-box. */
- public static final int KEYCODE_STB_POWER = 179;
- /** Key code constant: Set-top-box input key.
- * On TV remotes, switches the input mode on an external Set-top-box. */
- public static final int KEYCODE_STB_INPUT = 180;
- /** Key code constant: A/V Receiver power key.
- * On TV remotes, toggles the power on an external A/V Receiver. */
- public static final int KEYCODE_AVR_POWER = 181;
- /** Key code constant: A/V Receiver input key.
- * On TV remotes, switches the input mode on an external A/V Receiver. */
- public static final int KEYCODE_AVR_INPUT = 182;
- /** Key code constant: Red "programmable" key.
- * On TV remotes, acts as a contextual/programmable key. */
- public static final int KEYCODE_PROG_RED = 183;
- /** Key code constant: Green "programmable" key.
- * On TV remotes, actsas a contextual/programmable key. */
- public static final int KEYCODE_PROG_GREEN = 184;
- /** Key code constant: Yellow "programmable" key.
- * On TV remotes, acts as a contextual/programmable key. */
- public static final int KEYCODE_PROG_YELLOW = 185;
- /** Key code constant: Blue "programmable" key.
- * On TV remotes, acts as a contextual/programmable key. */
- public static final int KEYCODE_PROG_BLUE = 186;
-
- private static final int LAST_KEYCODE = KEYCODE_PROG_BLUE;
-
- private String[] mKeyCodes = new String[256];
- private String[] mAppKeyCodes = new String[256];
-
- private void initKeyCodes() {
- mKeyCodes[KEYCODE_DPAD_CENTER] = "\015";
- mKeyCodes[KEYCODE_DPAD_UP] = "\033[A";
- mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B";
- mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C";
- mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D";
- mKeyCodes[KEYCODE_F1] = "\033[OP";
- mKeyCodes[KEYCODE_F2] = "\033[OQ";
- mKeyCodes[KEYCODE_F3] = "\033[OR";
- mKeyCodes[KEYCODE_F4] = "\033[OS";
- mKeyCodes[KEYCODE_F5] = "\033[15~";
- mKeyCodes[KEYCODE_F6] = "\033[17~";
- mKeyCodes[KEYCODE_F7] = "\033[18~";
- mKeyCodes[KEYCODE_F8] = "\033[19~";
- mKeyCodes[KEYCODE_F9] = "\033[20~";
- mKeyCodes[KEYCODE_F10] = "\033[21~";
- mKeyCodes[KEYCODE_F11] = "\033[23~";
- mKeyCodes[KEYCODE_F12] = "\033[24~";
- mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print
- // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~";
- mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break
-
- mKeyCodes[KEYCODE_TAB] = "\011";
- mKeyCodes[KEYCODE_ENTER] = "\015";
- mKeyCodes[KEYCODE_ESCAPE] = "\033";
-
- mKeyCodes[KEYCODE_INSERT] = "\033[2~";
- mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~";
- mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~";
- mKeyCodes[KEYCODE_MOVE_END] = "\033[4~";
- mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~";
- mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~";
- mKeyCodes[KEYCODE_DEL]= "\177";
- mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP";
- mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/";
- mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*";
- mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-";
- mKeyCodes[KEYCODE_NUMPAD_ADD] = "+";
- mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015";
- mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "=";
- mKeyCodes[KEYCODE_NUMPAD_DOT] = ".";
- mKeyCodes[KEYCODE_NUMPAD_COMMA] = ",";
- mKeyCodes[KEYCODE_NUMPAD_0] = "0";
- mKeyCodes[KEYCODE_NUMPAD_1] = "1";
- mKeyCodes[KEYCODE_NUMPAD_2] = "2";
- mKeyCodes[KEYCODE_NUMPAD_3] = "3";
- mKeyCodes[KEYCODE_NUMPAD_4] = "4";
- mKeyCodes[KEYCODE_NUMPAD_5] = "5";
- mKeyCodes[KEYCODE_NUMPAD_6] = "6";
- mKeyCodes[KEYCODE_NUMPAD_7] = "7";
- mKeyCodes[KEYCODE_NUMPAD_8] = "8";
- mKeyCodes[KEYCODE_NUMPAD_9] = "9";
-
- mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA";
- mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB";
- mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC";
- mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD";
- mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo";
- mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj";
- mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om";
- mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok";
- mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM";
- mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX";
- mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On";
- mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol";
- mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op";
- mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq";
- mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or";
- mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os";
- mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot";
- mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou";
- mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov";
- mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow";
- mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox";
- mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy";
- }
-
- /**
- * The state engine for a modifier key. Can be pressed, released, locked,
- * and so on.
- *
- */
- private class ModifierKey {
-
- private int mState;
-
- private static final int UNPRESSED = 0;
-
- private static final int PRESSED = 1;
-
- private static final int RELEASED = 2;
-
- private static final int USED = 3;
-
- private static final int LOCKED = 4;
-
- /**
- * Construct a modifier key. UNPRESSED by default.
- *
- */
- public ModifierKey() {
- mState = UNPRESSED;
- }
-
- public void onPress() {
- switch (mState) {
- case PRESSED:
- // This is a repeat before use
- break;
- case RELEASED:
- mState = LOCKED;
- break;
- case USED:
- // This is a repeat after use
- break;
- case LOCKED:
- mState = UNPRESSED;
- break;
- default:
- mState = PRESSED;
- break;
- }
- }
-
- public void onRelease() {
- switch (mState) {
- case USED:
- mState = UNPRESSED;
- break;
- case PRESSED:
- mState = RELEASED;
- break;
- default:
- // Leave state alone
- break;
- }
- }
-
- public void adjustAfterKeypress() {
- switch (mState) {
- case PRESSED:
- mState = USED;
- break;
- case RELEASED:
- mState = UNPRESSED;
- break;
- default:
- // Leave state alone
- break;
- }
- }
-
- public boolean isActive() {
- return mState != UNPRESSED;
- }
- }
-
- private ModifierKey mAltKey = new ModifierKey();
-
- private ModifierKey mCapKey = new ModifierKey();
-
- private ModifierKey mControlKey = new ModifierKey();
-
- private boolean mCapsLock;
-
- /**
- * Construct a term key listener.
- *
- */
- public TermKeyListener() {
- initKeyCodes();
- }
-
- public void handleControlKey(boolean down) {
- if (down) {
- mControlKey.onPress();
- } else {
- mControlKey.onRelease();
- }
- }
-
- public int mapControlChar(int ch) {
- int result = ch;
- if (mControlKey.isActive()) {
- // Search is the control key.
- if (result >= 'a' && result <= 'z') {
- result = (char) (result - 'a' + '\001');
- } else if (result == ' ') {
- result = 0;
- } else if ((result == '[') || (result == '1')) {
- result = 27;
- } else if ((result == '\\') || (result == '.')) {
- result = 28;
- } else if ((result == ']') || (result == '0')) {
- result = 29;
- } else if ((result == '^') || (result == '6')) {
- result = 30; // control-^
- } else if ((result == '_') || (result == '5')) {
- result = 31;
- }
- }
-
- if (result > -1) {
- mAltKey.adjustAfterKeypress();
- mCapKey.adjustAfterKeypress();
- mControlKey.adjustAfterKeypress();
- }
- return result;
- }
-
- /**
- * Handle a keyDown event.
- *
- * @param keyCode the keycode of the keyDown event
- *
- */
- public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException {
- if (keyCode >= 0 && keyCode < mKeyCodes.length) {
- String code = null;
- if (appMode) {
- code = mAppKeyCodes[keyCode];
- }
- if (code == null) {
- code = mKeyCodes[keyCode];
- }
- if (code != null) {
- int length = code.length();
- for (int i = 0; i < length; i++) {
- out.write(code.charAt(i));
- }
- return;
- }
- }
- int result = -1;
- switch (keyCode) {
- case KeyEvent.KEYCODE_ALT_RIGHT:
- case KeyEvent.KEYCODE_ALT_LEFT:
- mAltKey.onPress();
- break;
-
- case KeyEvent.KEYCODE_SHIFT_LEFT:
- case KeyEvent.KEYCODE_SHIFT_RIGHT:
- mCapKey.onPress();
- break;
-
- case KEYCODE_CTRL_LEFT:
- case KEYCODE_CTRL_RIGHT:
- mControlKey.onPress();
- break;
-
- case KEYCODE_CAPS_LOCK:
- if (event.getRepeatCount() == 0) {
- mCapsLock = !mCapsLock;
- }
- break;
-
- default: {
- result = event.getUnicodeChar(
- (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) |
- (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0));
- break;
- }
- }
-
- result = mapControlChar(result);
-
- if (result >= 0) {
- out.write(result);
- }
- }
-
- /**
- * Handle a keyUp event.
- *
- * @param keyCode the keyCode of the keyUp event
- */
- public void keyUp(int keyCode) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_ALT_LEFT:
- case KeyEvent.KEYCODE_ALT_RIGHT:
- mAltKey.onRelease();
- break;
- case KeyEvent.KEYCODE_SHIFT_LEFT:
- case KeyEvent.KEYCODE_SHIFT_RIGHT:
- mCapKey.onRelease();
- break;
-
- case KEYCODE_CTRL_LEFT:
- case KEYCODE_CTRL_RIGHT:
- mControlKey.onRelease();
- break;
-
- default:
- // Ignore other keyUps
- break;
- }
- }
-}
diff --git a/term/.gitignore b/term/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/term/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/term/build.gradle b/term/build.gradle
new file mode 100644
index 000000000..3be608b9d
--- /dev/null
+++ b/term/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "jackpal.androidterm"
+ minSdkVersion 4
+ targetSdkVersion 22
+
+ ndk {
+ moduleName "libjackpal-androidterm5"
+ abiFilters 'armeabi', 'mips', 'x86'
+ ldLibs "log"
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
+
+dependencies {
+ compile project(':emulatorview')
+ compile project(':libtermexec')
+}
diff --git a/term/lint.xml b/term/lint.xml
new file mode 100644
index 000000000..45998cd64
--- /dev/null
+++ b/term/lint.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/term/src/main/AndroidManifest.xml b/term/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9b1c3f27d
--- /dev/null
+++ b/term/src/main/AndroidManifest.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/term/src/main/java/jackpal/androidterm/BoundSession.java b/term/src/main/java/jackpal/androidterm/BoundSession.java
new file mode 100644
index 000000000..20454fa58
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/BoundSession.java
@@ -0,0 +1,41 @@
+package jackpal.androidterm;
+
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import jackpal.androidterm.util.TermSettings;
+
+class BoundSession extends GenericTermSession {
+ private final String issuerTitle;
+
+ private boolean fullyInitialized;
+
+ BoundSession(ParcelFileDescriptor ptmxFd, TermSettings settings, String issuerTitle) {
+ super(ptmxFd, settings, true);
+
+ this.issuerTitle = issuerTitle;
+
+ setTermIn(new ParcelFileDescriptor.AutoCloseInputStream(ptmxFd));
+ setTermOut(new ParcelFileDescriptor.AutoCloseOutputStream(ptmxFd));
+ }
+
+ @Override
+ public String getTitle() {
+ final String extraTitle = super.getTitle();
+
+ return TextUtils.isEmpty(extraTitle)
+ ? issuerTitle
+ : issuerTitle + " — " + extraTitle;
+ }
+
+ @Override
+ public void initializeEmulator(int columns, int rows) {
+ super.initializeEmulator(columns, rows);
+
+ fullyInitialized = true;
+ }
+
+ @Override
+ boolean isFailFast() {
+ return !fullyInitialized;
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/Exec.java b/term/src/main/java/jackpal/androidterm/Exec.java
new file mode 100644
index 000000000..8db05b209
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/Exec.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.IllegalStateException;
+import java.lang.Process;
+import java.lang.reflect.Field;
+
+/**
+ * Utility methods for managing a pty file descriptor.
+ */
+public class Exec
+{
+ // Warning: bump the library revision, when an incompatible change happens
+ static {
+ System.loadLibrary("jackpal-androidterm5");
+ }
+
+ static native void setPtyWindowSizeInternal(int fd, int row, int col, int xpixel, int ypixel) throws IOException;
+
+ static native void setPtyUTF8ModeInternal(int fd, boolean utf8Mode) throws IOException;
+}
+
diff --git a/term/src/main/java/jackpal/androidterm/GenericTermSession.java b/term/src/main/java/jackpal/androidterm/GenericTermSession.java
new file mode 100644
index 000000000..9ead53764
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/GenericTermSession.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import jackpal.androidterm.emulatorview.ColorScheme;
+import jackpal.androidterm.emulatorview.TermSession;
+import jackpal.androidterm.emulatorview.UpdateCallback;
+
+import jackpal.androidterm.compat.FileCompat;
+import jackpal.androidterm.util.TermSettings;
+
+/**
+ * A terminal session, consisting of a TerminalEmulator, a TranscriptScreen,
+ * and the I/O streams used to talk to the process.
+ */
+class GenericTermSession extends TermSession {
+ //** Set to true to force into 80 x 24 for testing with vttest. */
+ private static final boolean VTTEST_MODE = false;
+
+ private static Field descriptorField;
+
+ private final long createdAt;
+
+ // A cookie which uniquely identifies this session.
+ private String mHandle;
+
+ final ParcelFileDescriptor mTermFd;
+
+ TermSettings mSettings;
+
+ public static final int PROCESS_EXIT_FINISHES_SESSION = 0;
+ public static final int PROCESS_EXIT_DISPLAYS_MESSAGE = 1;
+
+ private String mProcessExitMessage;
+
+ private UpdateCallback mUTF8ModeNotify = new UpdateCallback() {
+ public void onUpdate() {
+ setPtyUTF8Mode(getUTF8Mode());
+ }
+ };
+
+ GenericTermSession(ParcelFileDescriptor mTermFd, TermSettings settings, boolean exitOnEOF) {
+ super(exitOnEOF);
+
+ this.mTermFd = mTermFd;
+
+ this.createdAt = System.currentTimeMillis();
+
+ updatePrefs(settings);
+ }
+
+ public void updatePrefs(TermSettings settings) {
+ mSettings = settings;
+ setColorScheme(new ColorScheme(settings.getColorScheme()));
+ setDefaultUTF8Mode(settings.defaultToUTF8Mode());
+ }
+
+ @Override
+ public void initializeEmulator(int columns, int rows) {
+ if (VTTEST_MODE) {
+ columns = 80;
+ rows = 24;
+ }
+ super.initializeEmulator(columns, rows);
+
+ setPtyUTF8Mode(getUTF8Mode());
+ setUTF8ModeUpdateCallback(mUTF8ModeNotify);
+ }
+
+ @Override
+ public void updateSize(int columns, int rows) {
+ if (VTTEST_MODE) {
+ columns = 80;
+ rows = 24;
+ }
+ // Inform the attached pty of our new size:
+ setPtyWindowSize(rows, columns, 0, 0);
+ super.updateSize(columns, rows);
+ }
+
+ /* XXX We should really get this ourselves from the resource bundle, but
+ we cannot hold a context */
+ public void setProcessExitMessage(String message) {
+ mProcessExitMessage = message;
+ }
+
+ @Override
+ protected void onProcessExit() {
+ if (mSettings.closeWindowOnProcessExit()) {
+ finish();
+ } else if (mProcessExitMessage != null) {
+ try {
+ byte[] msg = ("\r\n[" + mProcessExitMessage + "]").getBytes("UTF-8");
+ appendToEmulator(msg, 0, msg.length);
+ notifyUpdate();
+ } catch (UnsupportedEncodingException e) {
+ // Never happens
+ }
+ }
+ }
+
+ @Override
+ public void finish() {
+ try {
+ mTermFd.close();
+ } catch (IOException e) {
+ // ok
+ }
+
+ super.finish();
+ }
+
+ /**
+ * Gets the terminal session's title. Unlike the superclass's getTitle(),
+ * if the title is null or an empty string, the provided default title will
+ * be returned instead.
+ *
+ * @param defaultTitle The default title to use if this session's title is
+ * unset or an empty string.
+ */
+ public String getTitle(String defaultTitle) {
+ String title = getTitle();
+ if (title != null && title.length() > 0) {
+ return title;
+ } else {
+ return defaultTitle;
+ }
+ }
+
+ public void setHandle(String handle) {
+ if (mHandle != null) {
+ throw new IllegalStateException("Cannot change handle once set");
+ }
+ mHandle = handle;
+ }
+
+ public String getHandle() {
+ return mHandle;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + '(' + createdAt + ',' + mHandle + ')';
+ }
+
+ /**
+ * Set the widow size for a given pty. Allows programs
+ * connected to the pty learn how large their screen is.
+ */
+ void setPtyWindowSize(int row, int col, int xpixel, int ypixel) {
+ // If the tty goes away too quickly, this may get called after it's descriptor is closed
+ if (!mTermFd.getFileDescriptor().valid())
+ return;
+
+ try {
+ Exec.setPtyWindowSizeInternal(getIntFd(mTermFd), row, col, xpixel, ypixel);
+ } catch (IOException e) {
+ Log.e("exec", "Failed to set window size: " + e.getMessage());
+
+ if (isFailFast())
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Set or clear UTF-8 mode for a given pty. Used by the terminal driver
+ * to implement correct erase behavior in cooked mode (Linux >= 2.6.4).
+ */
+ void setPtyUTF8Mode(boolean utf8Mode) {
+ // If the tty goes away too quickly, this may get called after it's descriptor is closed
+ if (!mTermFd.getFileDescriptor().valid())
+ return;
+
+ try {
+ Exec.setPtyUTF8ModeInternal(getIntFd(mTermFd), utf8Mode);
+ } catch (IOException e) {
+ Log.e("exec", "Failed to set UTF mode: " + e.getMessage());
+
+ if (isFailFast())
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * @return true, if failing to operate on file descriptor deserves an exception (never the case for ATE own shell)
+ */
+ boolean isFailFast() {
+ return false;
+ }
+
+ private static void cacheDescField() throws NoSuchFieldException {
+ if (descriptorField != null)
+ return;
+
+ descriptorField = FileDescriptor.class.getDeclaredField("descriptor");
+ descriptorField.setAccessible(true);
+ }
+
+ private static int getIntFd(ParcelFileDescriptor parcelFd) throws IOException {
+ if (Build.VERSION.SDK_INT >= 12)
+ return FdHelperHoneycomb.getFd(parcelFd);
+ else {
+ try {
+ cacheDescField();
+
+ return descriptorField.getInt(parcelFd.getFileDescriptor());
+ } catch (Exception e) {
+ throw new IOException("Unable to obtain file descriptor on this OS version: " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/RemoteInterface.java b/term/src/main/java/jackpal/androidterm/RemoteInterface.java
new file mode 100644
index 000000000..a595c59ff
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/RemoteInterface.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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 jackpal.androidterm;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.UUID;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import jackpal.androidterm.emulatorview.TermSession;
+
+import jackpal.androidterm.util.SessionList;
+import jackpal.androidterm.util.TermSettings;
+
+public class RemoteInterface extends Activity {
+ protected static final String PRIVACT_OPEN_NEW_WINDOW = "jackpal.androidterm.private.OPEN_NEW_WINDOW";
+ protected static final String PRIVACT_SWITCH_WINDOW = "jackpal.androidterm.private.SWITCH_WINDOW";
+
+ protected static final String PRIVEXTRA_TARGET_WINDOW = "jackpal.androidterm.private.target_window";
+
+ protected static final String PRIVACT_ACTIVITY_ALIAS = "jackpal.androidterm.TermInternal";
+
+ private TermSettings mSettings;
+
+ private TermService mTermService;
+ private Intent mTSIntent;
+ private ServiceConnection mTSConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ TermService.TSBinder binder = (TermService.TSBinder) service;
+ mTermService = binder.getService();
+ handleIntent();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ mTermService = null;
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mSettings = new TermSettings(getResources(), prefs);
+
+ Intent TSIntent = new Intent(this, TermService.class);
+ mTSIntent = TSIntent;
+ startService(TSIntent);
+ if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) {
+ Log.e(TermDebug.LOG_TAG, "bind to service failed!");
+ finish();
+ }
+ }
+
+ @Override
+ public void finish() {
+ ServiceConnection conn = mTSConnection;
+ if (conn != null) {
+ unbindService(conn);
+
+ // Stop the service if no terminal sessions are running
+ TermService service = mTermService;
+ if (service != null) {
+ SessionList sessions = service.getSessions();
+ if (sessions == null || sessions.size() == 0) {
+ stopService(mTSIntent);
+ }
+ }
+
+ mTSConnection = null;
+ mTermService = null;
+ }
+ super.finish();
+ }
+
+ protected TermService getTermService() {
+ return mTermService;
+ }
+
+ protected void handleIntent() {
+ TermService service = getTermService();
+ if (service == null) {
+ finish();
+ return;
+ }
+
+ Intent myIntent = getIntent();
+ String action = myIntent.getAction();
+ if (action.equals(Intent.ACTION_SEND)
+ && myIntent.hasExtra(Intent.EXTRA_STREAM)) {
+ /* "permission.RUN_SCRIPT" not required as this is merely opening a new window. */
+ Object extraStream = myIntent.getExtras().get(Intent.EXTRA_STREAM);
+ if (extraStream instanceof Uri) {
+ String path = ((Uri) extraStream).getPath();
+ File file = new File(path);
+ String dirPath = file.isDirectory() ? path : file.getParent();
+ openNewWindow("cd " + quoteForBash(dirPath));
+ }
+ } else {
+ // Intent sender may not have permissions, ignore any extras
+ openNewWindow(null);
+ }
+
+ finish();
+ }
+
+ /**
+ * Quote a string so it can be used as a parameter in bash and similar shells.
+ */
+ public static String quoteForBash(String s) {
+ StringBuilder builder = new StringBuilder();
+ String specialChars = "\"\\$`!";
+ builder.append('"');
+ int length = s.length();
+ for (int i = 0; i < length; i++) {
+ char c = s.charAt(i);
+ if (specialChars.indexOf(c) >= 0) {
+ builder.append('\\');
+ }
+ builder.append(c);
+ }
+ builder.append('"');
+ return builder.toString();
+ }
+
+ protected String openNewWindow(String iInitialCommand) {
+ TermService service = getTermService();
+
+ String initialCommand = mSettings.getInitialCommand();
+ if (iInitialCommand != null) {
+ if (initialCommand != null) {
+ initialCommand += "\r" + iInitialCommand;
+ } else {
+ initialCommand = iInitialCommand;
+ }
+ }
+
+ try {
+ TermSession session = Term.createTermSession(this, mSettings, initialCommand);
+
+ session.setFinishCallback(service);
+ service.getSessions().add(session);
+
+ String handle = UUID.randomUUID().toString();
+ ((GenericTermSession) session).setHandle(handle);
+
+ Intent intent = new Intent(PRIVACT_OPEN_NEW_WINDOW);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+
+ return handle;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ protected String appendToWindow(String handle, String iInitialCommand) {
+ TermService service = getTermService();
+
+ // Find the target window
+ SessionList sessions = service.getSessions();
+ GenericTermSession target = null;
+ int index;
+ for (index = 0; index < sessions.size(); ++index) {
+ GenericTermSession session = (GenericTermSession) sessions.get(index);
+ String h = session.getHandle();
+ if (h != null && h.equals(handle)) {
+ target = session;
+ break;
+ }
+ }
+
+ if (target == null) {
+ // Target window not found, open a new one
+ return openNewWindow(iInitialCommand);
+ }
+
+ if (iInitialCommand != null) {
+ target.write(iInitialCommand);
+ target.write('\r');
+ }
+
+ Intent intent = new Intent(PRIVACT_SWITCH_WINDOW);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(PRIVEXTRA_TARGET_WINDOW, index);
+ startActivity(intent);
+
+ return handle;
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/RunScript.java b/term/src/main/java/jackpal/androidterm/RunScript.java
new file mode 100644
index 000000000..41c0e7b5a
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/RunScript.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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 jackpal.androidterm;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+/*
+ * New procedure for launching a command in ATE.
+ * Build the path and arguments into a Uri and set that into Intent.data.
+ * intent.data(new Uri.Builder().setScheme("file").setPath(path).setFragment(arguments))
+ *
+ * The old procedure of using Intent.Extra is still available but is discouraged.
+ */
+public final class RunScript extends RemoteInterface {
+ private static final String ACTION_RUN_SCRIPT = "jackpal.androidterm.RUN_SCRIPT";
+
+ private static final String EXTRA_WINDOW_HANDLE = "jackpal.androidterm.window_handle";
+ private static final String EXTRA_INITIAL_COMMAND = "jackpal.androidterm.iInitialCommand";
+
+ @Override
+ protected void handleIntent() {
+ TermService service = getTermService();
+ if (service == null) {
+ finish();
+ return;
+ }
+
+ Intent myIntent = getIntent();
+ String action = myIntent.getAction();
+ if (action.equals(ACTION_RUN_SCRIPT)) {
+ /* Someone with the appropriate permissions has asked us to
+ run a script */
+ String handle = myIntent.getStringExtra(EXTRA_WINDOW_HANDLE);
+ String command=null;
+ /*
+ * First look in Intent.data for the path; if not there, revert to
+ * the EXTRA_INITIAL_COMMAND location.
+ */
+ Uri uri=myIntent.getData();
+ if(uri!=null) // scheme[path][arguments]
+ {
+ String s=uri.getScheme();
+ if(s!=null && s.toLowerCase().equals("file"))
+ {
+ command=uri.getPath();
+ // Allow for the command to be contained within the arguments string.
+ if(command==null) command="";
+ if(!command.equals("")) command=quoteForBash(command);
+ // Append any arguments.
+ if(null!=(s=uri.getFragment())) command+=" "+s;
+ }
+ }
+ // If Intent.data not used then fall back to old method.
+ if(command==null) command=myIntent.getStringExtra(EXTRA_INITIAL_COMMAND);
+ if (handle != null) {
+ // Target the request at an existing window if open
+ handle = appendToWindow(handle, command);
+ } else {
+ // Open a new window
+ handle = openNewWindow(command);
+ }
+ Intent result = new Intent();
+ result.putExtra(EXTRA_WINDOW_HANDLE, handle);
+ setResult(RESULT_OK, result);
+
+ finish();
+ } else {
+ super.handleIntent();
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/RunShortcut.java b/term/src/main/java/jackpal/androidterm/RunShortcut.java
new file mode 100644
index 000000000..13e5f9743
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/RunShortcut.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 Steven Luo
+ *
+ * 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 jackpal.androidterm;
+
+import jackpal.androidterm.util.ShortcutEncryption;
+
+import java.security.GeneralSecurityException;
+
+import android.content.Intent;
+import android.util.Log;
+
+public final class RunShortcut extends RemoteInterface {
+ public static final String ACTION_RUN_SHORTCUT = "jackpal.androidterm.RUN_SHORTCUT";
+
+ public static final String EXTRA_WINDOW_HANDLE = "jackpal.androidterm.window_handle";
+ public static final String EXTRA_SHORTCUT_COMMAND = "jackpal.androidterm.iShortcutCommand";
+
+ @Override
+ protected void handleIntent() {
+ TermService service = getTermService();
+ if (service == null) {
+ finish();
+ return;
+ }
+
+ Intent myIntent = getIntent();
+ String action = myIntent.getAction();
+ if (action.equals(ACTION_RUN_SHORTCUT)) {
+ String encCommand = myIntent.getStringExtra(EXTRA_SHORTCUT_COMMAND);
+ if (encCommand == null) {
+ Log.e(TermDebug.LOG_TAG, "No command provided in shortcut!");
+ finish();
+ return;
+ }
+
+ // Decrypt and verify the command
+ ShortcutEncryption.Keys keys = ShortcutEncryption.getKeys(this);
+ if (keys == null) {
+ // No keys -- no valid shortcuts can exist
+ Log.e(TermDebug.LOG_TAG, "No shortcut encryption keys found!");
+ finish();
+ return;
+ }
+ String command;
+ try {
+ command = ShortcutEncryption.decrypt(encCommand, keys);
+ } catch (GeneralSecurityException e) {
+ Log.e(TermDebug.LOG_TAG, "Invalid shortcut: " + e.toString());
+ finish();
+ return;
+ }
+
+ String handle = myIntent.getStringExtra(EXTRA_WINDOW_HANDLE);
+ if (handle != null) {
+ // Target the request at an existing window if open
+ handle = appendToWindow(handle, command);
+ } else {
+ // Open a new window
+ handle = openNewWindow(command);
+ }
+ Intent result = new Intent();
+ result.putExtra(EXTRA_WINDOW_HANDLE, handle);
+ setResult(RESULT_OK, result);
+ }
+
+ finish();
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/ShellTermSession.java b/term/src/main/java/jackpal/androidterm/ShellTermSession.java
new file mode 100644
index 000000000..91133e8b3
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/ShellTermSession.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import jackpal.androidterm.compat.FileCompat;
+import jackpal.androidterm.util.TermSettings;
+
+import java.io.*;
+import java.util.ArrayList;
+
+/**
+ * A terminal session, controlling the process attached to the session (usually
+ * a shell). It keeps track of process PID and destroys it's process group
+ * upon stopping.
+ */
+public class ShellTermSession extends GenericTermSession {
+ private int mProcId;
+ private Thread mWatcherThread;
+
+ private String mInitialCommand;
+
+ private static final int PROCESS_EXITED = 1;
+ private Handler mMsgHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (!isRunning()) {
+ return;
+ }
+ if (msg.what == PROCESS_EXITED) {
+ onProcessExit((Integer) msg.obj);
+ }
+ }
+ };
+
+ public ShellTermSession(TermSettings settings, String initialCommand) throws IOException {
+ super(ParcelFileDescriptor.open(new File("/dev/ptmx"), ParcelFileDescriptor.MODE_READ_WRITE),
+ settings, false);
+
+ initializeSession();
+
+ setTermOut(new ParcelFileDescriptor.AutoCloseOutputStream(mTermFd));
+ setTermIn(new ParcelFileDescriptor.AutoCloseInputStream(mTermFd));
+
+ mInitialCommand = initialCommand;
+
+ mWatcherThread = new Thread() {
+ @Override
+ public void run() {
+ Log.i(TermDebug.LOG_TAG, "waiting for: " + mProcId);
+ int result = TermExec.waitFor(mProcId);
+ Log.i(TermDebug.LOG_TAG, "Subprocess exited: " + result);
+ mMsgHandler.sendMessage(mMsgHandler.obtainMessage(PROCESS_EXITED, result));
+ }
+ };
+ mWatcherThread.setName("Process watcher");
+ }
+
+ private void initializeSession() throws IOException {
+ TermSettings settings = mSettings;
+
+ String path = System.getenv("PATH");
+ if (settings.doPathExtensions()) {
+ String appendPath = settings.getAppendPath();
+ if (appendPath != null && appendPath.length() > 0) {
+ path = path + ":" + appendPath;
+ }
+
+ if (settings.allowPathPrepend()) {
+ String prependPath = settings.getPrependPath();
+ if (prependPath != null && prependPath.length() > 0) {
+ path = prependPath + ":" + path;
+ }
+ }
+ }
+ if (settings.verifyPath()) {
+ path = checkPath(path);
+ }
+ String[] env = new String[3];
+ env[0] = "TERM=" + settings.getTermType();
+ env[1] = "PATH=" + path;
+ env[2] = "HOME=" + settings.getHomePath();
+
+ mProcId = createSubprocess(settings.getShell(), env);
+ }
+
+ private String checkPath(String path) {
+ String[] dirs = path.split(":");
+ StringBuilder checkedPath = new StringBuilder(path.length());
+ for (String dirname : dirs) {
+ File dir = new File(dirname);
+ if (dir.isDirectory() && FileCompat.canExecute(dir)) {
+ checkedPath.append(dirname);
+ checkedPath.append(":");
+ }
+ }
+ return checkedPath.substring(0, checkedPath.length()-1);
+ }
+
+ @Override
+ public void initializeEmulator(int columns, int rows) {
+ super.initializeEmulator(columns, rows);
+
+ mWatcherThread.start();
+ sendInitialCommand(mInitialCommand);
+ }
+
+ private void sendInitialCommand(String initialCommand) {
+ if (initialCommand.length() > 0) {
+ write(initialCommand + '\r');
+ }
+ }
+
+ private int createSubprocess(String shell, String[] env) throws IOException {
+ ArrayList argList = parse(shell);
+ String arg0;
+ String[] args;
+
+ try {
+ arg0 = argList.get(0);
+ File file = new File(arg0);
+ if (!file.exists()) {
+ Log.e(TermDebug.LOG_TAG, "Shell " + arg0 + " not found!");
+ throw new FileNotFoundException(arg0);
+ } else if (!FileCompat.canExecute(file)) {
+ Log.e(TermDebug.LOG_TAG, "Shell " + arg0 + " not executable!");
+ throw new FileNotFoundException(arg0);
+ }
+ args = argList.toArray(new String[1]);
+ } catch (Exception e) {
+ argList = parse(mSettings.getFailsafeShell());
+ arg0 = argList.get(0);
+ args = argList.toArray(new String[1]);
+ }
+
+ return TermExec.createSubprocess(mTermFd, arg0, args, env);
+ }
+
+ private ArrayList parse(String cmd) {
+ final int PLAIN = 0;
+ final int WHITESPACE = 1;
+ final int INQUOTE = 2;
+ int state = WHITESPACE;
+ ArrayList result = new ArrayList();
+ int cmdLen = cmd.length();
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < cmdLen; i++) {
+ char c = cmd.charAt(i);
+ if (state == PLAIN) {
+ if (Character.isWhitespace(c)) {
+ result.add(builder.toString());
+ builder.delete(0,builder.length());
+ state = WHITESPACE;
+ } else if (c == '"') {
+ state = INQUOTE;
+ } else {
+ builder.append(c);
+ }
+ } else if (state == WHITESPACE) {
+ if (Character.isWhitespace(c)) {
+ // do nothing
+ } else if (c == '"') {
+ state = INQUOTE;
+ } else {
+ state = PLAIN;
+ builder.append(c);
+ }
+ } else if (state == INQUOTE) {
+ if (c == '\\') {
+ if (i + 1 < cmdLen) {
+ i += 1;
+ builder.append(cmd.charAt(i));
+ }
+ } else if (c == '"') {
+ state = PLAIN;
+ } else {
+ builder.append(c);
+ }
+ }
+ }
+ if (builder.length() > 0) {
+ result.add(builder.toString());
+ }
+ return result;
+ }
+
+ private void onProcessExit(int result) {
+ onProcessExit();
+ }
+
+ @Override
+ public void finish() {
+ hangupProcessGroup();
+ super.finish();
+ }
+
+ /**
+ * Send SIGHUP to a process group, SIGHUP notifies a terminal client, that the terminal have been disconnected,
+ * and usually results in client's death, unless it's process is a daemon or have been somehow else detached
+ * from the terminal (for example, by the "nohup" utility).
+ */
+ void hangupProcessGroup() {
+ TermExec.sendSignal(-mProcId, 1);
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/Term.java b/term/src/main/java/jackpal/androidterm/Term.java
new file mode 100644
index 000000000..9eeb8474e
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/Term.java
@@ -0,0 +1,1142 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm;
+
+import android.text.TextUtils;
+import jackpal.androidterm.compat.ActionBarCompat;
+import jackpal.androidterm.compat.ActivityCompat;
+import jackpal.androidterm.compat.AndroidCompat;
+import jackpal.androidterm.compat.MenuItemCompat;
+import jackpal.androidterm.emulatorview.EmulatorView;
+import jackpal.androidterm.emulatorview.TermSession;
+import jackpal.androidterm.emulatorview.UpdateCallback;
+import jackpal.androidterm.emulatorview.compat.ClipboardManagerCompat;
+import jackpal.androidterm.emulatorview.compat.ClipboardManagerCompatFactory;
+import jackpal.androidterm.emulatorview.compat.KeycodeConstants;
+import jackpal.androidterm.util.SessionList;
+import jackpal.androidterm.util.TermSettings;
+
+import java.io.IOException;
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+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.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.preference.PreferenceManager;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * A terminal emulator activity.
+ */
+
+public class Term extends Activity implements UpdateCallback, SharedPreferences.OnSharedPreferenceChangeListener {
+ /**
+ * The ViewFlipper which holds the collection of EmulatorView widgets.
+ */
+ private TermViewFlipper mViewFlipper;
+
+ /**
+ * The name of the ViewFlipper in the resources.
+ */
+ private static final int VIEW_FLIPPER = R.id.view_flipper;
+
+ private SessionList mTermSessions;
+
+ private TermSettings mSettings;
+
+ private final static int SELECT_TEXT_ID = 0;
+ private final static int COPY_ALL_ID = 1;
+ private final static int PASTE_ID = 2;
+ private final static int SEND_CONTROL_KEY_ID = 3;
+ private final static int SEND_FN_KEY_ID = 4;
+
+ private boolean mAlreadyStarted = false;
+ private boolean mStopServiceOnFinish = false;
+
+ private Intent TSIntent;
+
+ public static final int REQUEST_CHOOSE_WINDOW = 1;
+ public static final String EXTRA_WINDOW_ID = "jackpal.androidterm.window_id";
+ private int onResumeSelectWindow = -1;
+ private ComponentName mPrivateAlias;
+
+ private PowerManager.WakeLock mWakeLock;
+ private WifiManager.WifiLock mWifiLock;
+ // Available on API 12 and later
+ private static final int WIFI_MODE_FULL_HIGH_PERF = 3;
+
+ private boolean mBackKeyPressed;
+
+ private static final String ACTION_PATH_BROADCAST = "jackpal.androidterm.broadcast.APPEND_TO_PATH";
+ private static final String ACTION_PATH_PREPEND_BROADCAST = "jackpal.androidterm.broadcast.PREPEND_TO_PATH";
+ private static final String PERMISSION_PATH_BROADCAST = "jackpal.androidterm.permission.APPEND_TO_PATH";
+ private static final String PERMISSION_PATH_PREPEND_BROADCAST = "jackpal.androidterm.permission.PREPEND_TO_PATH";
+ private int mPendingPathBroadcasts = 0;
+ private BroadcastReceiver mPathReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String path = makePathFromBundle(getResultExtras(false));
+ if (intent.getAction().equals(ACTION_PATH_PREPEND_BROADCAST)) {
+ mSettings.setPrependPath(path);
+ } else {
+ mSettings.setAppendPath(path);
+ }
+ mPendingPathBroadcasts--;
+
+ if (mPendingPathBroadcasts <= 0 && mTermService != null) {
+ populateViewFlipper();
+ populateWindowList();
+ }
+ }
+ };
+ // Available on API 12 and later
+ private static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x20;
+
+ private TermService mTermService;
+ private ServiceConnection mTSConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.i(TermDebug.LOG_TAG, "Bound to TermService");
+ TermService.TSBinder binder = (TermService.TSBinder) service;
+ mTermService = binder.getService();
+ if (mPendingPathBroadcasts <= 0) {
+ populateViewFlipper();
+ populateWindowList();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName arg0) {
+ mTermService = null;
+ }
+ };
+
+ private ActionBarCompat mActionBar;
+ private int mActionBarMode = TermSettings.ACTION_BAR_MODE_NONE;
+
+ private WindowListAdapter mWinListAdapter;
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ mSettings.readPrefs(sharedPreferences);
+ }
+
+ private class WindowListActionBarAdapter extends WindowListAdapter implements UpdateCallback {
+ // From android.R.style in API 13
+ private static final int TextAppearance_Holo_Widget_ActionBar_Title = 0x01030112;
+
+ public WindowListActionBarAdapter(SessionList sessions) {
+ super(sessions);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView label = new TextView(Term.this);
+ String title = getSessionTitle(position, getString(R.string.window_title, position + 1));
+ label.setText(title);
+ if (AndroidCompat.SDK >= 13) {
+ label.setTextAppearance(Term.this, TextAppearance_Holo_Widget_ActionBar_Title);
+ } else {
+ label.setTextAppearance(Term.this, android.R.style.TextAppearance_Medium);
+ }
+ return label;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ return super.getView(position, convertView, parent);
+ }
+
+ public void onUpdate() {
+ notifyDataSetChanged();
+ mActionBar.setSelectedNavigationItem(mViewFlipper.getDisplayedChild());
+ }
+ }
+
+ private ActionBarCompat.OnNavigationListener mWinListItemSelected = new ActionBarCompat.OnNavigationListener() {
+ public boolean onNavigationItemSelected(int position, long id) {
+ int oldPosition = mViewFlipper.getDisplayedChild();
+ if (position != oldPosition) {
+ if (position >= mViewFlipper.getChildCount()) {
+ mViewFlipper.addView(createEmulatorView(mTermSessions.get(position)));
+ }
+ mViewFlipper.setDisplayedChild(position);
+ if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES) {
+ mActionBar.hide();
+ }
+ }
+ return true;
+ }
+ };
+
+ private boolean mHaveFullHwKeyboard = false;
+
+ private class EmulatorViewGestureListener extends SimpleOnGestureListener {
+ private EmulatorView view;
+
+ public EmulatorViewGestureListener(EmulatorView view) {
+ this.view = view;
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ // Let the EmulatorView handle taps if mouse tracking is active
+ if (view.isMouseTrackingActive()) return false;
+
+ //Check for link at tap location
+ String link = view.getURLat(e.getX(), e.getY());
+ if(link != null)
+ execURL(link);
+ else
+ doUIToggle((int) e.getX(), (int) e.getY(), view.getVisibleWidth(), view.getVisibleHeight());
+ return true;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ float absVelocityX = Math.abs(velocityX);
+ float absVelocityY = Math.abs(velocityY);
+ if (absVelocityX > Math.max(1000.0f, 2.0 * absVelocityY)) {
+ // Assume user wanted side to side movement
+ if (velocityX > 0) {
+ // Left to right swipe -- previous window
+ mViewFlipper.showPrevious();
+ } else {
+ // Right to left swipe -- next window
+ mViewFlipper.showNext();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Should we use keyboard shortcuts?
+ */
+ private boolean mUseKeyboardShortcuts;
+
+ /**
+ * Intercepts keys before the view/terminal gets it.
+ */
+ private View.OnKeyListener mKeyListener = new View.OnKeyListener() {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ return backkeyInterceptor(keyCode, event) || keyboardShortcuts(keyCode, event);
+ }
+
+ /**
+ * Keyboard shortcuts (tab management, paste)
+ */
+ private boolean keyboardShortcuts(int keyCode, KeyEvent event) {
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return false;
+ }
+ if (!mUseKeyboardShortcuts) {
+ return false;
+ }
+ boolean isCtrlPressed = (event.getMetaState() & KeycodeConstants.META_CTRL_ON) != 0;
+ boolean isShiftPressed = (event.getMetaState() & KeycodeConstants.META_SHIFT_ON) != 0;
+
+ if (keyCode == KeycodeConstants.KEYCODE_TAB && isCtrlPressed) {
+ if (isShiftPressed) {
+ mViewFlipper.showPrevious();
+ } else {
+ mViewFlipper.showNext();
+ }
+
+ return true;
+ } else if (keyCode == KeycodeConstants.KEYCODE_N && isCtrlPressed && isShiftPressed) {
+ doCreateNewWindow();
+
+ return true;
+ } else if (keyCode == KeycodeConstants.KEYCODE_V && isCtrlPressed && isShiftPressed) {
+ doPaste();
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Make sure the back button always leaves the application.
+ */
+ private boolean backkeyInterceptor(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES && mActionBar != null && mActionBar.isShowing()) {
+ /* We need to intercept the key event before the view sees it,
+ otherwise the view will handle it before we get it */
+ onKeyUp(keyCode, event);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+
+ private Handler mHandler = new Handler();
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ Log.v(TermDebug.LOG_TAG, "onCreate");
+
+ mPrivateAlias = new ComponentName(this, RemoteInterface.PRIVACT_ACTIVITY_ALIAS);
+
+ if (icicle == null)
+ onNewIntent(getIntent());
+
+ final SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mSettings = new TermSettings(getResources(), mPrefs);
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
+
+ Intent broadcast = new Intent(ACTION_PATH_BROADCAST);
+ if (AndroidCompat.SDK >= 12) {
+ broadcast.addFlags(FLAG_INCLUDE_STOPPED_PACKAGES);
+ }
+ mPendingPathBroadcasts++;
+ sendOrderedBroadcast(broadcast, PERMISSION_PATH_BROADCAST, mPathReceiver, null, RESULT_OK, null, null);
+
+ broadcast = new Intent(broadcast);
+ broadcast.setAction(ACTION_PATH_PREPEND_BROADCAST);
+ mPendingPathBroadcasts++;
+ sendOrderedBroadcast(broadcast, PERMISSION_PATH_PREPEND_BROADCAST, mPathReceiver, null, RESULT_OK, null, null);
+
+ TSIntent = new Intent(this, TermService.class);
+ startService(TSIntent);
+
+ if (AndroidCompat.SDK >= 11) {
+ int actionBarMode = mSettings.actionBarMode();
+ mActionBarMode = actionBarMode;
+ if (AndroidCompat.V11ToV20) {
+ switch (actionBarMode) {
+ case TermSettings.ACTION_BAR_MODE_ALWAYS_VISIBLE:
+ setTheme(R.style.Theme_Holo);
+ break;
+ case TermSettings.ACTION_BAR_MODE_HIDES:
+ setTheme(R.style.Theme_Holo_ActionBarOverlay);
+ break;
+ }
+ }
+ } else {
+ mActionBarMode = TermSettings.ACTION_BAR_MODE_ALWAYS_VISIBLE;
+ }
+
+ setContentView(R.layout.term_activity);
+ mViewFlipper = (TermViewFlipper) findViewById(VIEW_FLIPPER);
+
+ PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermDebug.LOG_TAG);
+ WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
+ int wifiLockMode = WifiManager.WIFI_MODE_FULL;
+ if (AndroidCompat.SDK >= 12) {
+ wifiLockMode = WIFI_MODE_FULL_HIGH_PERF;
+ }
+ mWifiLock = wm.createWifiLock(wifiLockMode, TermDebug.LOG_TAG);
+
+ ActionBarCompat actionBar = ActivityCompat.getActionBar(this);
+ if (actionBar != null) {
+ mActionBar = actionBar;
+ actionBar.setNavigationMode(ActionBarCompat.NAVIGATION_MODE_LIST);
+ actionBar.setDisplayOptions(0, ActionBarCompat.DISPLAY_SHOW_TITLE);
+ if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES) {
+ actionBar.hide();
+ }
+ }
+
+ mHaveFullHwKeyboard = checkHaveFullHwKeyboard(getResources().getConfiguration());
+
+ updatePrefs();
+ mAlreadyStarted = true;
+ }
+
+ private String makePathFromBundle(Bundle extras) {
+ if (extras == null || extras.size() == 0) {
+ return "";
+ }
+
+ String[] keys = new String[extras.size()];
+ keys = extras.keySet().toArray(keys);
+ Collator collator = Collator.getInstance(Locale.US);
+ Arrays.sort(keys, collator);
+
+ StringBuilder path = new StringBuilder();
+ for (String key : keys) {
+ String dir = extras.getString(key);
+ if (dir != null && !dir.equals("")) {
+ path.append(dir);
+ path.append(":");
+ }
+ }
+
+ return path.substring(0, path.length()-1);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) {
+ throw new IllegalStateException("Failed to bind to TermService!");
+ }
+ }
+
+ private void populateViewFlipper() {
+ if (mTermService != null) {
+ mTermSessions = mTermService.getSessions();
+
+ if (mTermSessions.size() == 0) {
+ try {
+ mTermSessions.add(createTermSession());
+ } catch (IOException e) {
+ Toast.makeText(this, "Failed to start terminal session", Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+ }
+
+ mTermSessions.addCallback(this);
+
+ for (TermSession session : mTermSessions) {
+ EmulatorView view = createEmulatorView(session);
+ mViewFlipper.addView(view);
+ }
+
+ updatePrefs();
+
+ if (onResumeSelectWindow >= 0) {
+ mViewFlipper.setDisplayedChild(onResumeSelectWindow);
+ onResumeSelectWindow = -1;
+ }
+ mViewFlipper.onResume();
+ }
+ }
+
+ private void populateWindowList() {
+ if (mActionBar == null) {
+ // Not needed
+ return;
+ }
+ if (mTermSessions != null) {
+ int position = mViewFlipper.getDisplayedChild();
+ if (mWinListAdapter == null) {
+ mWinListAdapter = new WindowListActionBarAdapter(mTermSessions);
+
+ mActionBar.setListNavigationCallbacks(mWinListAdapter, mWinListItemSelected);
+ } else {
+ mWinListAdapter.setSessions(mTermSessions);
+ }
+ mViewFlipper.addCallback(mWinListAdapter);
+
+ mActionBar.setSelectedNavigationItem(position);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .unregisterOnSharedPreferenceChangeListener(this);
+
+ if (mStopServiceOnFinish) {
+ stopService(TSIntent);
+ }
+ mTermService = null;
+ mTSConnection = null;
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ if (mWifiLock.isHeld()) {
+ mWifiLock.release();
+ }
+ }
+
+ private void restart() {
+ startActivity(getIntent());
+ finish();
+ }
+
+ protected static TermSession createTermSession(Context context, TermSettings settings, String initialCommand) throws IOException {
+ GenericTermSession session = new ShellTermSession(settings, initialCommand);
+ // XXX We should really be able to fetch this from within TermSession
+ session.setProcessExitMessage(context.getString(R.string.process_exit_message));
+
+ return session;
+ }
+
+ private TermSession createTermSession() throws IOException {
+ TermSettings settings = mSettings;
+ TermSession session = createTermSession(this, settings, settings.getInitialCommand());
+ session.setFinishCallback(mTermService);
+ return session;
+ }
+
+ private TermView createEmulatorView(TermSession session) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ TermView emulatorView = new TermView(this, session, metrics);
+
+ emulatorView.setExtGestureListener(new EmulatorViewGestureListener(emulatorView));
+ emulatorView.setOnKeyListener(mKeyListener);
+ registerForContextMenu(emulatorView);
+
+ return emulatorView;
+ }
+
+ private TermSession getCurrentTermSession() {
+ SessionList sessions = mTermSessions;
+ if (sessions == null) {
+ return null;
+ } else {
+ return sessions.get(mViewFlipper.getDisplayedChild());
+ }
+ }
+
+ private EmulatorView getCurrentEmulatorView() {
+ return (EmulatorView) mViewFlipper.getCurrentView();
+ }
+
+ private void updatePrefs() {
+ mUseKeyboardShortcuts = mSettings.getUseKeyboardShortcutsFlag();
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+ mViewFlipper.updatePrefs(mSettings);
+
+ for (View v : mViewFlipper) {
+ ((EmulatorView) v).setDensity(metrics);
+ ((TermView) v).updatePrefs(mSettings);
+ }
+
+ if (mTermSessions != null) {
+ for (TermSession session : mTermSessions) {
+ ((GenericTermSession) session).updatePrefs(mSettings);
+ }
+ }
+
+ {
+ Window win = getWindow();
+ WindowManager.LayoutParams params = win.getAttributes();
+ final int FULLSCREEN = WindowManager.LayoutParams.FLAG_FULLSCREEN;
+ int desiredFlag = mSettings.showStatusBar() ? 0 : FULLSCREEN;
+ if (desiredFlag != (params.flags & FULLSCREEN) || (AndroidCompat.SDK >= 11 && mActionBarMode != mSettings.actionBarMode())) {
+ if (mAlreadyStarted) {
+ // Can't switch to/from fullscreen after
+ // starting the activity.
+ restart();
+ } else {
+ win.setFlags(desiredFlag, FULLSCREEN);
+ if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES) {
+ if (mActionBar != null) {
+ mActionBar.hide();
+ }
+ }
+ }
+ }
+ }
+
+ int orientation = mSettings.getScreenOrientation();
+ int o = 0;
+ if (orientation == 0) {
+ o = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ } else if (orientation == 1) {
+ o = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ } else if (orientation == 2) {
+ o = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ } else {
+ /* Shouldn't be happened. */
+ }
+ setRequestedOrientation(o);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ if (AndroidCompat.SDK < 5) {
+ /* If we lose focus between a back key down and a back key up,
+ we shouldn't respond to the next back key up event unless
+ we get another key down first */
+ mBackKeyPressed = false;
+ }
+
+ /* Explicitly close the input method
+ Otherwise, the soft keyboard could cover up whatever activity takes
+ our place */
+ final IBinder token = mViewFlipper.getWindowToken();
+ new Thread() {
+ @Override
+ public void run() {
+ InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(token, 0);
+ }
+ }.start();
+ }
+
+ @Override
+ protected void onStop() {
+ mViewFlipper.onPause();
+ if (mTermSessions != null) {
+ mTermSessions.removeCallback(this);
+
+ if (mWinListAdapter != null) {
+ mTermSessions.removeCallback(mWinListAdapter);
+ mTermSessions.removeTitleChangedListener(mWinListAdapter);
+ mViewFlipper.removeCallback(mWinListAdapter);
+ }
+ }
+
+ mViewFlipper.removeAllViews();
+
+ unbindService(mTSConnection);
+
+ super.onStop();
+ }
+
+ private boolean checkHaveFullHwKeyboard(Configuration c) {
+ return (c.keyboard == Configuration.KEYBOARD_QWERTY) &&
+ (c.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ mHaveFullHwKeyboard = checkHaveFullHwKeyboard(newConfig);
+
+ EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView();
+ if (v != null) {
+ v.updateSize(false);
+ }
+
+ if (mWinListAdapter != null) {
+ // Force Android to redraw the label in the navigation dropdown
+ mWinListAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main, menu);
+ MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_new_window), MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
+ MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_close_window), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == R.id.menu_preferences) {
+ doPreferences();
+ } else if (id == R.id.menu_new_window) {
+ doCreateNewWindow();
+ } else if (id == R.id.menu_close_window) {
+ confirmCloseWindow();
+ } else if (id == R.id.menu_window_list) {
+ startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW);
+ } else if (id == R.id.menu_reset) {
+ doResetTerminal();
+ Toast toast = Toast.makeText(this,R.string.reset_toast_notification,Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER, 0, 0);
+ toast.show();
+ } else if (id == R.id.menu_send_email) {
+ doEmailTranscript();
+ } else if (id == R.id.menu_special_keys) {
+ doDocumentKeys();
+ } else if (id == R.id.menu_toggle_soft_keyboard) {
+ doToggleSoftKeyboard();
+ } else if (id == R.id.menu_toggle_wakelock) {
+ doToggleWakeLock();
+ } else if (id == R.id.menu_toggle_wifilock) {
+ doToggleWifiLock();
+ } else if (id == R.id.action_help) {
+ Intent openHelp = new Intent(Intent.ACTION_VIEW,
+ Uri.parse(getString(R.string.help_url)));
+ startActivity(openHelp);
+ }
+ // Hide the action bar if appropriate
+ if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES) {
+ mActionBar.hide();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void doCreateNewWindow() {
+ if (mTermSessions == null) {
+ Log.w(TermDebug.LOG_TAG, "Couldn't create new window because mTermSessions == null");
+ return;
+ }
+
+ try {
+ TermSession session = createTermSession();
+
+ mTermSessions.add(session);
+
+ TermView view = createEmulatorView(session);
+ view.updatePrefs(mSettings);
+
+ mViewFlipper.addView(view);
+ mViewFlipper.setDisplayedChild(mViewFlipper.getChildCount()-1);
+ } catch (IOException e) {
+ Toast.makeText(this, "Failed to create a session", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void confirmCloseWindow() {
+ final AlertDialog.Builder b = new AlertDialog.Builder(this);
+ b.setIcon(android.R.drawable.ic_dialog_alert);
+ b.setMessage(R.string.confirm_window_close_message);
+ final Runnable closeWindow = new Runnable() {
+ public void run() {
+ doCloseWindow();
+ }
+ };
+ b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ mHandler.post(closeWindow);
+ }
+ });
+ b.setNegativeButton(android.R.string.no, null);
+ b.show();
+ }
+
+ private void doCloseWindow() {
+ if (mTermSessions == null) {
+ return;
+ }
+
+ EmulatorView view = getCurrentEmulatorView();
+ if (view == null) {
+ return;
+ }
+ TermSession session = mTermSessions.remove(mViewFlipper.getDisplayedChild());
+ view.onPause();
+ session.finish();
+ mViewFlipper.removeView(view);
+ if (mTermSessions.size() != 0) {
+ mViewFlipper.showNext();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int request, int result, Intent data) {
+ switch (request) {
+ case REQUEST_CHOOSE_WINDOW:
+ if (result == RESULT_OK && data != null) {
+ int position = data.getIntExtra(EXTRA_WINDOW_ID, -2);
+ if (position >= 0) {
+ // Switch windows after session list is in sync, not here
+ onResumeSelectWindow = position;
+ } else if (position == -1) {
+ doCreateNewWindow();
+ onResumeSelectWindow = mTermSessions.size() - 1;
+ }
+ } else {
+ // Close the activity if user closed all sessions
+ // TODO the left path will be invoked when nothing happened, but this Activity was destroyed!
+ if (mTermSessions == null || mTermSessions.size() == 0) {
+ mStopServiceOnFinish = true;
+ finish();
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
+ // Don't repeat action if intent comes from history
+ return;
+ }
+
+ String action = intent.getAction();
+ if (TextUtils.isEmpty(action) || !mPrivateAlias.equals(intent.getComponent())) {
+ return;
+ }
+
+ // huge number simply opens new window
+ // TODO: add a way to restrict max number of windows per caller (possibly via reusing BoundSession)
+ switch (action) {
+ case RemoteInterface.PRIVACT_OPEN_NEW_WINDOW:
+ onResumeSelectWindow = Integer.MAX_VALUE;
+ break;
+ case RemoteInterface.PRIVACT_SWITCH_WINDOW:
+ int target = intent.getIntExtra(RemoteInterface.PRIVEXTRA_TARGET_WINDOW, -1);
+ if (target >= 0) {
+ onResumeSelectWindow = target;
+ }
+ break;
+ }
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem wakeLockItem = menu.findItem(R.id.menu_toggle_wakelock);
+ MenuItem wifiLockItem = menu.findItem(R.id.menu_toggle_wifilock);
+ if (mWakeLock.isHeld()) {
+ wakeLockItem.setTitle(R.string.disable_wakelock);
+ } else {
+ wakeLockItem.setTitle(R.string.enable_wakelock);
+ }
+ if (mWifiLock.isHeld()) {
+ wifiLockItem.setTitle(R.string.disable_wifilock);
+ } else {
+ wifiLockItem.setTitle(R.string.enable_wifilock);
+ }
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ menu.setHeaderTitle(R.string.edit_text);
+ menu.add(0, SELECT_TEXT_ID, 0, R.string.select_text);
+ menu.add(0, COPY_ALL_ID, 0, R.string.copy_all);
+ menu.add(0, PASTE_ID, 0, R.string.paste);
+ menu.add(0, SEND_CONTROL_KEY_ID, 0, R.string.send_control_key);
+ menu.add(0, SEND_FN_KEY_ID, 0, R.string.send_fn_key);
+ if (!canPaste()) {
+ menu.getItem(PASTE_ID).setEnabled(false);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case SELECT_TEXT_ID:
+ getCurrentEmulatorView().toggleSelectingText();
+ return true;
+ case COPY_ALL_ID:
+ doCopyAll();
+ return true;
+ case PASTE_ID:
+ doPaste();
+ return true;
+ case SEND_CONTROL_KEY_ID:
+ doSendControlKey();
+ return true;
+ case SEND_FN_KEY_ID:
+ doSendFnKey();
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ /* The pre-Eclair default implementation of onKeyDown() would prevent
+ our handling of the Back key in onKeyUp() from taking effect, so
+ ignore it here */
+ if (AndroidCompat.SDK < 5 && keyCode == KeyEvent.KEYCODE_BACK) {
+ /* Android pre-Eclair has no key event tracking, and a back key
+ down event delivered to an activity above us in the back stack
+ could be succeeded by a back key up event to us, so we need to
+ keep track of our own back key presses */
+ mBackKeyPressed = true;
+ return true;
+ } else {
+ return super.onKeyDown(keyCode, event);
+ }
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ if (AndroidCompat.SDK < 5) {
+ if (!mBackKeyPressed) {
+ /* This key up event might correspond to a key down
+ delivered to another activity -- ignore */
+ return false;
+ }
+ mBackKeyPressed = false;
+ }
+ if (mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES && mActionBar != null && mActionBar.isShowing()) {
+ mActionBar.hide();
+ return true;
+ }
+ switch (mSettings.getBackKeyAction()) {
+ case TermSettings.BACK_KEY_STOPS_SERVICE:
+ mStopServiceOnFinish = true;
+ case TermSettings.BACK_KEY_CLOSES_ACTIVITY:
+ finish();
+ return true;
+ case TermSettings.BACK_KEY_CLOSES_WINDOW:
+ doCloseWindow();
+ return true;
+ default:
+ return false;
+ }
+ case KeyEvent.KEYCODE_MENU:
+ if (mActionBar != null && !mActionBar.isShowing()) {
+ mActionBar.show();
+ return true;
+ } else {
+ return super.onKeyUp(keyCode, event);
+ }
+ default:
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+
+ // Called when the list of sessions changes
+ public void onUpdate() {
+ SessionList sessions = mTermSessions;
+ if (sessions == null) {
+ return;
+ }
+
+ if (sessions.size() == 0) {
+ mStopServiceOnFinish = true;
+ finish();
+ } else if (sessions.size() < mViewFlipper.getChildCount()) {
+ for (int i = 0; i < mViewFlipper.getChildCount(); ++i) {
+ EmulatorView v = (EmulatorView) mViewFlipper.getChildAt(i);
+ if (!sessions.contains(v.getTermSession())) {
+ v.onPause();
+ mViewFlipper.removeView(v);
+ --i;
+ }
+ }
+ }
+ }
+
+ private boolean canPaste() {
+ ClipboardManagerCompat clip = ClipboardManagerCompatFactory
+ .getManager(getApplicationContext());
+ if (clip.hasText()) {
+ return true;
+ }
+ return false;
+ }
+
+ private void doPreferences() {
+ startActivity(new Intent(this, TermPreferences.class));
+ }
+
+ private void doResetTerminal() {
+ TermSession session = getCurrentTermSession();
+ if (session != null) {
+ session.reset();
+ }
+ }
+
+ private void doEmailTranscript() {
+ TermSession session = getCurrentTermSession();
+ if (session != null) {
+ // Don't really want to supply an address, but
+ // currently it's required, otherwise nobody
+ // wants to handle the intent.
+ String addr = "user@example.com";
+ Intent intent =
+ new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
+ + addr));
+
+ String subject = getString(R.string.email_transcript_subject);
+ String title = session.getTitle();
+ if (title != null) {
+ subject = subject + " - " + title;
+ }
+ intent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ intent.putExtra(Intent.EXTRA_TEXT,
+ session.getTranscriptText().trim());
+ try {
+ startActivity(Intent.createChooser(intent,
+ getString(R.string.email_transcript_chooser_title)));
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this,
+ R.string.email_transcript_no_email_activity_found,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ private void doCopyAll() {
+ ClipboardManagerCompat clip = ClipboardManagerCompatFactory
+ .getManager(getApplicationContext());
+ clip.setText(getCurrentTermSession().getTranscriptText().trim());
+ }
+
+ private void doPaste() {
+ if (!canPaste()) {
+ return;
+ }
+ ClipboardManagerCompat clip = ClipboardManagerCompatFactory
+ .getManager(getApplicationContext());
+ CharSequence paste = clip.getText();
+ getCurrentTermSession().write(paste.toString());
+ }
+
+ private void doSendControlKey() {
+ getCurrentEmulatorView().sendControlKey();
+ }
+
+ private void doSendFnKey() {
+ getCurrentEmulatorView().sendFnKey();
+ }
+
+ private void doDocumentKeys() {
+ AlertDialog.Builder dialog = new AlertDialog.Builder(this);
+ Resources r = getResources();
+ dialog.setTitle(r.getString(R.string.control_key_dialog_title));
+ dialog.setMessage(
+ formatMessage(mSettings.getControlKeyId(), TermSettings.CONTROL_KEY_ID_NONE,
+ r, R.array.control_keys_short_names,
+ R.string.control_key_dialog_control_text,
+ R.string.control_key_dialog_control_disabled_text, "CTRLKEY")
+ + "\n\n" +
+ formatMessage(mSettings.getFnKeyId(), TermSettings.FN_KEY_ID_NONE,
+ r, R.array.fn_keys_short_names,
+ R.string.control_key_dialog_fn_text,
+ R.string.control_key_dialog_fn_disabled_text, "FNKEY"));
+ dialog.show();
+ }
+
+ private String formatMessage(int keyId, int disabledKeyId,
+ Resources r, int arrayId,
+ int enabledId,
+ int disabledId, String regex) {
+ if (keyId == disabledKeyId) {
+ return r.getString(disabledId);
+ }
+ String[] keyNames = r.getStringArray(arrayId);
+ String keyName = keyNames[keyId];
+ String template = r.getString(enabledId);
+ String result = template.replaceAll(regex, keyName);
+ return result;
+ }
+
+ private void doToggleSoftKeyboard() {
+ InputMethodManager imm = (InputMethodManager)
+ getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
+
+ }
+
+ private void doToggleWakeLock() {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ } else {
+ mWakeLock.acquire();
+ }
+ ActivityCompat.invalidateOptionsMenu(this);
+ }
+
+ private void doToggleWifiLock() {
+ if (mWifiLock.isHeld()) {
+ mWifiLock.release();
+ } else {
+ mWifiLock.acquire();
+ }
+ ActivityCompat.invalidateOptionsMenu(this);
+ }
+
+ private void doToggleActionBar() {
+ ActionBarCompat bar = mActionBar;
+ if (bar == null) {
+ return;
+ }
+ if (bar.isShowing()) {
+ bar.hide();
+ } else {
+ bar.show();
+ }
+ }
+
+ private void doUIToggle(int x, int y, int width, int height) {
+ switch (mActionBarMode) {
+ case TermSettings.ACTION_BAR_MODE_NONE:
+ if (AndroidCompat.SDK >= 11 && (mHaveFullHwKeyboard || y < height / 2)) {
+ openOptionsMenu();
+ return;
+ } else {
+ doToggleSoftKeyboard();
+ }
+ break;
+ case TermSettings.ACTION_BAR_MODE_ALWAYS_VISIBLE:
+ if (!mHaveFullHwKeyboard) {
+ doToggleSoftKeyboard();
+ }
+ break;
+ case TermSettings.ACTION_BAR_MODE_HIDES:
+ if (mHaveFullHwKeyboard || y < height / 2) {
+ doToggleActionBar();
+ return;
+ } else {
+ doToggleSoftKeyboard();
+ }
+ break;
+ }
+ getCurrentEmulatorView().requestFocus();
+ }
+
+ /**
+ *
+ * Send a URL up to Android to be handled by a browser.
+ * @param link The URL to be opened.
+ */
+ private void execURL(String link)
+ {
+ Uri webLink = Uri.parse(link);
+ Intent openLink = new Intent(Intent.ACTION_VIEW, webLink);
+ PackageManager pm = getPackageManager();
+ List handlers = pm.queryIntentActivities(openLink, 0);
+ if(handlers.size() > 0)
+ startActivity(openLink);
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/TermDebug.java b/term/src/main/java/jackpal/androidterm/TermDebug.java
new file mode 100644
index 000000000..f9888b8d3
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/TermDebug.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm;
+
+/**
+ * Debug settings.
+ */
+
+public class TermDebug {
+ /**
+ * Set to true to add debugging code and logging.
+ */
+ public static final boolean DEBUG = false;
+
+ /**
+ * The tag we use when logging, so that our messages can be distinguished
+ * from other messages in the log. Public because it's used by several
+ * classes.
+ */
+ public static final String LOG_TAG = "Term";
+}
diff --git a/term/src/main/java/jackpal/androidterm/TermPreferences.java b/term/src/main/java/jackpal/androidterm/TermPreferences.java
new file mode 100644
index 000000000..ef8f410c0
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/TermPreferences.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm;
+
+import jackpal.androidterm.compat.ActionBarCompat;
+import jackpal.androidterm.compat.ActivityCompat;
+import jackpal.androidterm.compat.AndroidCompat;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
+import android.view.MenuItem;
+
+public class TermPreferences extends PreferenceActivity {
+ private static final String ACTIONBAR_KEY = "actionbar";
+ private static final String CATEGORY_SCREEN_KEY = "screen";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.preferences);
+
+ // Remove the action bar pref on older platforms without an action bar
+ if (AndroidCompat.SDK < 11) {
+ Preference actionBarPref = findPreference(ACTIONBAR_KEY);
+ PreferenceCategory screenCategory =
+ (PreferenceCategory) findPreference(CATEGORY_SCREEN_KEY);
+ if ((actionBarPref != null) && (screenCategory != null)) {
+ screenCategory.removePreference(actionBarPref);
+ }
+ }
+
+ // Display up indicator on action bar home button
+ if (AndroidCompat.V11ToV20) {
+ ActionBarCompat bar = ActivityCompat.getActionBar(this);
+ if (bar != null) {
+ bar.setDisplayOptions(ActionBarCompat.DISPLAY_HOME_AS_UP, ActionBarCompat.DISPLAY_HOME_AS_UP);
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case ActionBarCompat.ID_HOME:
+ // Action bar home button selected
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/TermService.java b/term/src/main/java/jackpal/androidterm/TermService.java
new file mode 100644
index 000000000..d75eff6e9
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/TermService.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm;
+
+import android.app.Service;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.*;
+import android.content.Intent;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.app.Notification;
+import android.app.PendingIntent;
+
+import jackpal.androidterm.emulatorview.TermSession;
+
+import jackpal.androidterm.compat.ServiceForegroundCompat;
+import jackpal.androidterm.libtermexec.v1.*;
+import jackpal.androidterm.util.SessionList;
+import jackpal.androidterm.util.TermSettings;
+
+import java.util.UUID;
+
+public class TermService extends Service implements TermSession.FinishCallback
+{
+ /* Parallels the value of START_STICKY on API Level >= 5 */
+ private static final int COMPAT_START_STICKY = 1;
+
+ private static final int RUNNING_NOTIFICATION = 1;
+ private ServiceForegroundCompat compat;
+
+ private SessionList mTermSessions;
+
+ public class TSBinder extends Binder {
+ TermService getService() {
+ Log.i("TermService", "Activity binding to service");
+ return TermService.this;
+ }
+ }
+ private final IBinder mTSBinder = new TSBinder();
+
+ @Override
+ public void onStart(Intent intent, int flags) {
+ }
+
+ /* This should be @Override if building with API Level >=5 */
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return COMPAT_START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (TermExec.SERVICE_ACTION_V1.equals(intent.getAction())) {
+ Log.i("TermService", "Outside process called onBind()");
+
+ return new RBinder();
+ } else {
+ Log.i("TermService", "Activity called onBind()");
+
+ return mTSBinder;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ // should really belong to the Application class, but we don't use one...
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ SharedPreferences.Editor editor = prefs.edit();
+ String defValue = getDir("HOME", MODE_PRIVATE).getAbsolutePath();
+ String homePath = prefs.getString("home_path", defValue);
+ editor.putString("home_path", homePath);
+ editor.commit();
+
+ compat = new ServiceForegroundCompat(this);
+ mTermSessions = new SessionList();
+
+ /* Put the service in the foreground. */
+ Notification notification = new Notification(R.drawable.ic_stat_service_notification_icon, getText(R.string.service_notify_text), System.currentTimeMillis());
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+ Intent notifyIntent = new Intent(this, Term.class);
+ notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0);
+ notification.setLatestEventInfo(this, getText(R.string.application_terminal), getText(R.string.service_notify_text), pendingIntent);
+ compat.startForeground(RUNNING_NOTIFICATION, notification);
+
+ Log.d(TermDebug.LOG_TAG, "TermService started");
+ return;
+ }
+
+ @Override
+ public void onDestroy() {
+ compat.stopForeground(true);
+ for (TermSession session : mTermSessions) {
+ /* Don't automatically remove from list of sessions -- we clear the
+ * list below anyway and we could trigger
+ * ConcurrentModificationException if we do */
+ session.setFinishCallback(null);
+ session.finish();
+ }
+ mTermSessions.clear();
+ return;
+ }
+
+ public SessionList getSessions() {
+ return mTermSessions;
+ }
+
+ public void onSessionFinish(TermSession session) {
+ mTermSessions.remove(session);
+ }
+
+ private final class RBinder extends ITerminal.Stub {
+ @Override
+ public IntentSender startSession(final ParcelFileDescriptor pseudoTerminalMultiplexerFd,
+ final ResultReceiver callback) {
+ final String sessionHandle = UUID.randomUUID().toString();
+
+ // distinct Intent Uri and PendingIntent requestCode must be sufficient to avoid collisions
+ final Intent switchIntent = new Intent(RemoteInterface.PRIVACT_OPEN_NEW_WINDOW)
+ .setData(Uri.parse(sessionHandle))
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(RemoteInterface.PRIVEXTRA_TARGET_WINDOW, sessionHandle);
+
+ final PendingIntent result = PendingIntent.getActivity(getApplicationContext(), sessionHandle.hashCode(),
+ switchIntent, 0);
+
+ final PackageManager pm = getPackageManager();
+ final String[] pkgs = pm.getPackagesForUid(getCallingUid());
+ if (pkgs == null || pkgs.length == 0)
+ return null;
+
+ for (String packageName:pkgs) {
+ try {
+ final PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
+
+ final ApplicationInfo appInfo = pkgInfo.applicationInfo;
+ if (appInfo == null)
+ continue;
+
+ final CharSequence label = pm.getApplicationLabel(appInfo);
+
+ if (!TextUtils.isEmpty(label)) {
+ final String niceName = label.toString();
+
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ GenericTermSession session = null;
+ try {
+ final TermSettings settings = new TermSettings(getResources(),
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
+
+ session = new BoundSession(pseudoTerminalMultiplexerFd, settings, niceName);
+
+ mTermSessions.add(session);
+
+ session.setHandle(sessionHandle);
+ session.setFinishCallback(new RBinderCleanupCallback(result, callback));
+ session.setTitle("");
+
+ session.initializeEmulator(80, 24);
+ } catch (Exception whatWentWrong) {
+ Log.e("TermService", "Failed to bootstrap AIDL session: "
+ + whatWentWrong.getMessage());
+
+ if (session != null)
+ session.finish();
+ }
+ }
+ });
+
+ return result.getIntentSender();
+ }
+ } catch (PackageManager.NameNotFoundException ignore) {}
+ }
+
+ return null;
+ }
+ }
+
+ private final class RBinderCleanupCallback implements TermSession.FinishCallback {
+ private final PendingIntent result;
+ private final ResultReceiver callback;
+
+ public RBinderCleanupCallback(PendingIntent result, ResultReceiver callback) {
+ this.result = result;
+ this.callback = callback;
+ }
+
+ @Override
+ public void onSessionFinish(TermSession session) {
+ result.cancel();
+
+ callback.send(0, new Bundle());
+
+ mTermSessions.remove(session);
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/TermView.java b/term/src/main/java/jackpal/androidterm/TermView.java
new file mode 100644
index 000000000..ec5e1eb37
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/TermView.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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 jackpal.androidterm;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+
+import jackpal.androidterm.emulatorview.ColorScheme;
+import jackpal.androidterm.emulatorview.EmulatorView;
+import jackpal.androidterm.emulatorview.TermSession;
+
+import jackpal.androidterm.util.TermSettings;
+
+public class TermView extends EmulatorView {
+ public TermView(Context context, TermSession session, DisplayMetrics metrics) {
+ super(context, session, metrics);
+ }
+
+ public void updatePrefs(TermSettings settings, ColorScheme scheme) {
+ if (scheme == null) {
+ scheme = new ColorScheme(settings.getColorScheme());
+ }
+
+ setTextSize(settings.getFontSize());
+ setUseCookedIME(settings.useCookedIME());
+ setColorScheme(scheme);
+ setBackKeyCharacter(settings.getBackKeyCharacter());
+ setAltSendsEsc(settings.getAltSendsEscFlag());
+ setControlKeyCode(settings.getControlKeyCode());
+ setFnKeyCode(settings.getFnKeyCode());
+ setTermType(settings.getTermType());
+ setMouseTracking(settings.getMouseTrackingFlag());
+ }
+
+ public void updatePrefs(TermSettings settings) {
+ updatePrefs(settings, null);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().toString() + '(' + getTermSession() + ')';
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/TermViewFlipper.java b/term/src/main/java/jackpal/androidterm/TermViewFlipper.java
new file mode 100644
index 000000000..ea901978e
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/TermViewFlipper.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2011 Steven Luo
+ *
+ * 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 jackpal.androidterm;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Toast;
+import android.widget.ViewFlipper;
+
+import jackpal.androidterm.emulatorview.EmulatorView;
+import jackpal.androidterm.emulatorview.TermSession;
+import jackpal.androidterm.emulatorview.UpdateCallback;
+
+import jackpal.androidterm.compat.AndroidCompat;
+import jackpal.androidterm.util.TermSettings;
+
+public class TermViewFlipper extends ViewFlipper implements Iterable {
+ private Context context;
+ private Toast mToast;
+ private LinkedList callbacks;
+ private boolean mStatusBarVisible = false;
+
+ private int mCurWidth;
+ private int mCurHeight;
+ private Rect mVisibleRect = new Rect();
+ private Rect mWindowRect = new Rect();
+ private LayoutParams mChildParams = null;
+ private boolean mRedoLayout = false;
+
+ /**
+ * True if we must poll to discover if the view has changed size.
+ * This is the only known way to detect the view changing size due to
+ * the IME being shown or hidden in API level <= 7.
+ */
+ private final boolean mbPollForWindowSizeChange = (AndroidCompat.SDK < 8);
+ private static final int SCREEN_CHECK_PERIOD = 1000;
+ private final Handler mHandler = new Handler();
+ private Runnable mCheckSize = new Runnable() {
+ public void run() {
+ adjustChildSize();
+ mHandler.postDelayed(this, SCREEN_CHECK_PERIOD);
+ }
+ };
+
+ class ViewFlipperIterator implements Iterator {
+ int pos = 0;
+
+ public boolean hasNext() {
+ return (pos < getChildCount());
+ }
+
+ public View next() {
+ return getChildAt(pos++);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public TermViewFlipper(Context context) {
+ super(context);
+ commonConstructor(context);
+ }
+
+ public TermViewFlipper(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ commonConstructor(context);
+ }
+
+ private void commonConstructor(Context context) {
+ this.context = context;
+ callbacks = new LinkedList();
+
+ updateVisibleRect();
+ Rect visible = mVisibleRect;
+ mChildParams = new LayoutParams(visible.width(), visible.height(),
+ Gravity.TOP|Gravity.LEFT);
+ }
+
+ public void updatePrefs(TermSettings settings) {
+ boolean statusBarVisible = settings.showStatusBar();
+ int[] colorScheme = settings.getColorScheme();
+ setBackgroundColor(colorScheme[1]);
+ mStatusBarVisible = statusBarVisible;
+ }
+
+ public Iterator iterator() {
+ return new ViewFlipperIterator();
+ }
+
+ public void addCallback(UpdateCallback callback) {
+ callbacks.add(callback);
+ }
+
+ public void removeCallback(UpdateCallback callback) {
+ callbacks.remove(callback);
+ }
+
+ private void notifyChange() {
+ for (UpdateCallback callback : callbacks) {
+ callback.onUpdate();
+ }
+ }
+
+ public void onPause() {
+ if (mbPollForWindowSizeChange) {
+ mHandler.removeCallbacks(mCheckSize);
+ }
+ pauseCurrentView();
+ }
+
+ public void onResume() {
+ if (mbPollForWindowSizeChange) {
+ mCheckSize.run();
+ }
+ resumeCurrentView();
+ }
+
+ public void pauseCurrentView() {
+ EmulatorView view = (EmulatorView) getCurrentView();
+ if (view == null) {
+ return;
+ }
+ view.onPause();
+ }
+
+ public void resumeCurrentView() {
+ EmulatorView view = (EmulatorView) getCurrentView();
+ if (view == null) {
+ return;
+ }
+ view.onResume();
+ view.requestFocus();
+ }
+
+ private void showTitle() {
+ if (getChildCount() == 0) {
+ return;
+ }
+
+ EmulatorView view = (EmulatorView) getCurrentView();
+ if (view == null) {
+ return;
+ }
+ TermSession session = view.getTermSession();
+ if (session == null) {
+ return;
+ }
+
+ String title = context.getString(R.string.window_title,getDisplayedChild()+1);
+ if (session instanceof GenericTermSession) {
+ title = ((GenericTermSession) session).getTitle(title);
+ }
+
+ if (mToast == null) {
+ mToast = Toast.makeText(context, title, Toast.LENGTH_SHORT);
+ mToast.setGravity(Gravity.CENTER, 0, 0);
+ } else {
+ mToast.setText(title);
+ }
+ mToast.show();
+ }
+
+ @Override
+ public void showPrevious() {
+ pauseCurrentView();
+ super.showPrevious();
+ showTitle();
+ resumeCurrentView();
+ notifyChange();
+ }
+
+ @Override
+ public void showNext() {
+ pauseCurrentView();
+ super.showNext();
+ showTitle();
+ resumeCurrentView();
+ notifyChange();
+ }
+
+ @Override
+ public void setDisplayedChild(int position) {
+ pauseCurrentView();
+ super.setDisplayedChild(position);
+ showTitle();
+ resumeCurrentView();
+ notifyChange();
+ }
+
+ @Override
+ public void addView(View v, int index) {
+ super.addView(v, index, mChildParams);
+ }
+
+ @Override
+ public void addView(View v) {
+ super.addView(v, mChildParams);
+ }
+
+ private void updateVisibleRect() {
+ Rect visible = mVisibleRect;
+ Rect window = mWindowRect;
+
+ /* Get rectangle representing visible area of this view, as seen by
+ the activity (takes other views in the layout into account, but
+ not space used by the IME) */
+ getGlobalVisibleRect(visible);
+
+ /* Get rectangle representing visible area of this window (takes
+ IME into account, but not other views in the layout) */
+ getWindowVisibleDisplayFrame(window);
+ /* Work around bug in getWindowVisibleDisplayFrame on API < 10, and
+ avoid a distracting height change as status bar hides otherwise */
+ if (!mStatusBarVisible) {
+ window.top = 0;
+ }
+
+ // Clip visible rectangle's top to the visible portion of the window
+ if (visible.width() == 0 && visible.height() == 0) {
+ visible.left = window.left;
+ visible.top = window.top;
+ } else {
+ if (visible.left < window.left) {
+ visible.left = window.left;
+ }
+ if (visible.top < window.top) {
+ visible.top = window.top;
+ }
+ }
+ // Always set the bottom of the rectangle to the window bottom
+ /* XXX This breaks with a split action bar, but if we don't do this,
+ it's possible that the view won't resize correctly on IME hide */
+ visible.right = window.right;
+ visible.bottom = window.bottom;
+ }
+
+ private void adjustChildSize() {
+ updateVisibleRect();
+ Rect visible = mVisibleRect;
+ int width = visible.width();
+ int height = visible.height();
+
+ if (mCurWidth != width || mCurHeight != height) {
+ mCurWidth = width;
+ mCurHeight = height;
+
+ LayoutParams params = mChildParams;
+ params.width = width;
+ params.height = height;
+ for (View v : this) {
+ updateViewLayout(v, params);
+ }
+ mRedoLayout = true;
+
+ EmulatorView currentView = (EmulatorView) getCurrentView();
+ if (currentView != null) {
+ currentView.updateSize(false);
+ }
+ }
+ }
+
+ /**
+ * Called when the view changes size.
+ * (Note: Not always called on Android < 2.2)
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ adjustChildSize();
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mRedoLayout) {
+ requestLayout();
+ mRedoLayout = false;
+ }
+ super.onDraw(canvas);
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/WindowList.java b/term/src/main/java/jackpal/androidterm/WindowList.java
new file mode 100644
index 000000000..16cf42b33
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/WindowList.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2011 Steven Luo
+ *
+ * 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 jackpal.androidterm;
+
+import android.app.ListActivity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ListView;
+
+import jackpal.androidterm.compat.ActionBarCompat;
+import jackpal.androidterm.compat.ActivityCompat;
+import jackpal.androidterm.compat.AndroidCompat;
+import jackpal.androidterm.util.SessionList;
+
+public class WindowList extends ListActivity {
+ private SessionList sessions;
+ private WindowListAdapter mWindowListAdapter;
+ private TermService mTermService;
+
+ /**
+ * View which isn't automatically in the pressed state if its parent is
+ * pressed. This allows the window's entry to be pressed without the close
+ * button being triggered.
+ * Idea and code shamelessly borrowed from the Android browser's tabs list.
+ *
+ * Used by layout xml.
+ */
+ public static class CloseButton extends ImageView {
+ public CloseButton(Context context) {
+ super(context);
+ }
+
+ public CloseButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CloseButton(Context context, AttributeSet attrs, int style) {
+ super(context, attrs, style);
+ }
+
+ @Override
+ public void setPressed(boolean pressed) {
+ if (pressed && ((View) getParent()).isPressed()) {
+ return;
+ }
+ super.setPressed(pressed);
+ }
+ }
+
+ private ServiceConnection mTSConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ TermService.TSBinder binder = (TermService.TSBinder) service;
+ mTermService = binder.getService();
+ populateList();
+ }
+
+ public void onServiceDisconnected(ComponentName arg0) {
+ mTermService = null;
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ ListView listView = getListView();
+ View newWindow = getLayoutInflater().inflate(R.layout.window_list_new_window, listView, false);
+ listView.addHeaderView(newWindow, null, true);
+
+ setResult(RESULT_CANCELED);
+
+ // Display up indicator on action bar home button
+ if (AndroidCompat.SDK >= 11) {
+ ActionBarCompat bar = ActivityCompat.getActionBar(this);
+ if (bar != null) {
+ bar.setDisplayOptions(ActionBarCompat.DISPLAY_HOME_AS_UP, ActionBarCompat.DISPLAY_HOME_AS_UP);
+ }
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ Intent TSIntent = new Intent(this, TermService.class);
+ if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) {
+ Log.w(TermDebug.LOG_TAG, "bind to service failed!");
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ WindowListAdapter adapter = mWindowListAdapter;
+ if (sessions != null) {
+ sessions.removeCallback(adapter);
+ sessions.removeTitleChangedListener(adapter);
+ }
+ if (adapter != null) {
+ adapter.setSessions(null);
+ }
+ unbindService(mTSConnection);
+ }
+
+ private void populateList() {
+ sessions = mTermService.getSessions();
+ WindowListAdapter adapter = mWindowListAdapter;
+
+ if (adapter == null) {
+ adapter = new WindowListAdapter(sessions);
+ setListAdapter(adapter);
+ mWindowListAdapter = adapter;
+ } else {
+ adapter.setSessions(sessions);
+ }
+ sessions.addCallback(adapter);
+ sessions.addTitleChangedListener(adapter);
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Intent data = new Intent();
+ data.putExtra(Term.EXTRA_WINDOW_ID, position-1);
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case ActionBarCompat.ID_HOME:
+ // Action bar home button selected
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/WindowListAdapter.java b/term/src/main/java/jackpal/androidterm/WindowListAdapter.java
new file mode 100644
index 000000000..a96b6c949
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/WindowListAdapter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2011 Steven Luo
+ *
+ * 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 jackpal.androidterm;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import jackpal.androidterm.emulatorview.TermSession;
+import jackpal.androidterm.emulatorview.UpdateCallback;
+
+import jackpal.androidterm.util.SessionList;
+
+public class WindowListAdapter extends BaseAdapter implements UpdateCallback {
+ private SessionList mSessions;
+
+ public WindowListAdapter(SessionList sessions) {
+ setSessions(sessions);
+ }
+
+ public void setSessions(SessionList sessions) {
+ mSessions = sessions;
+
+ if (sessions != null) {
+ sessions.addCallback(this);
+ sessions.addTitleChangedListener(this);
+ } else {
+ onUpdate();
+ }
+ }
+
+ public int getCount() {
+ if (mSessions != null) {
+ return mSessions.size();
+ } else {
+ return 0;
+ }
+ }
+
+ public Object getItem(int position) {
+ return mSessions.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ protected String getSessionTitle(int position, String defaultTitle) {
+ TermSession session = mSessions.get(position);
+ if (session != null && session instanceof GenericTermSession) {
+ return ((GenericTermSession) session).getTitle(defaultTitle);
+ } else {
+ return defaultTitle;
+ }
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Activity act = findActivityFromContext(parent.getContext());
+ View child = act.getLayoutInflater().inflate(R.layout.window_list_item, parent, false);
+ View close = child.findViewById(R.id.window_list_close);
+
+ TextView label = (TextView) child.findViewById(R.id.window_list_label);
+ String defaultTitle = act.getString(R.string.window_title, position+1);
+ label.setText(getSessionTitle(position, defaultTitle));
+
+ final SessionList sessions = mSessions;
+ final int closePosition = position;
+ close.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ TermSession session = sessions.remove(closePosition);
+ if (session != null) {
+ session.finish();
+ notifyDataSetChanged();
+ }
+ }
+ });
+
+ return child;
+ }
+
+ public void onUpdate() {
+ notifyDataSetChanged();
+ }
+
+ private static Activity findActivityFromContext(Context context) {
+ if (context == null) {
+ return null;
+ } else if (context instanceof Activity) {
+ return (Activity) context;
+ } else if (context instanceof ContextWrapper) {
+ ContextWrapper cw = (ContextWrapper) context;
+ return findActivityFromContext(cw.getBaseContext());
+ }
+ return null;
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/compat/ActionBarCompat.java b/term/src/main/java/jackpal/androidterm/compat/ActionBarCompat.java
new file mode 100644
index 000000000..1f951e35f
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/compat/ActionBarCompat.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011 Steven Luo
+ *
+ * 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 jackpal.androidterm.compat;
+
+import android.app.ActionBar;
+import android.widget.SpinnerAdapter;
+
+/**
+ * Provides ActionBar APIs.
+ */
+public abstract class ActionBarCompat {
+ public static final int NAVIGATION_MODE_STANDARD = 0;
+ public static final int NAVIGATION_MODE_LIST = 1;
+ public static final int NAVIGATION_MODE_TABS = 2;
+ public static final int DISPLAY_USE_LOGO = 1;
+ public static final int DISPLAY_SHOW_HOME = 2;
+ public static final int DISPLAY_HOME_AS_UP = 4;
+ public static final int DISPLAY_SHOW_TITLE = 8;
+ public static final int DISPLAY_SHOW_CUSTOM = 16;
+
+ // Provides android.R.id.home from API 11 and up
+ public static final int ID_HOME = 0x0102002c;
+
+ public interface OnNavigationListener {
+ public abstract boolean onNavigationItemSelected(int position, long id);
+ }
+
+ public static ActionBarCompat wrap(Object actionBar) {
+ if (actionBar != null) {
+ if (AndroidCompat.SDK >= 11) {
+ return new ActionBarApi11OrLater(actionBar);
+ }
+ }
+ return null;
+ }
+
+ public abstract int getDisplayOptions();
+ public abstract int getHeight();
+ public abstract int getNavigationItemCount();
+ public abstract int getNavigationMode();
+ public abstract int getSelectedNavigationIndex();
+ public abstract CharSequence getTitle();
+ public abstract void hide();
+ public abstract boolean isShowing();
+ public abstract void setDisplayOptions(int options);
+ public abstract void setDisplayOptions(int options, int mask);
+ public abstract void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback);
+ public abstract void setNavigationMode(int mode);
+ public abstract void setSelectedNavigationItem(int position);
+ public abstract void setTitle(int resId);
+ public abstract void setTitle(CharSequence title);
+ public abstract void show();
+}
+
+class ActionBarApi11OrLater extends ActionBarCompat {
+ private ActionBar bar;
+
+ ActionBarApi11OrLater(Object bar) {
+ this.bar = (ActionBar) bar;
+ }
+
+ private ActionBar.OnNavigationListener wrapOnNavigationCallback(OnNavigationListener callback) {
+ final OnNavigationListener cb = callback;
+ return new ActionBar.OnNavigationListener() {
+ public boolean onNavigationItemSelected(int position, long id) {
+ return cb.onNavigationItemSelected(position, id);
+ }
+ };
+ }
+
+ public int getDisplayOptions() {
+ return bar.getDisplayOptions();
+ }
+
+ public int getHeight() {
+ return bar.getHeight();
+ }
+
+ public int getNavigationItemCount() {
+ return bar.getNavigationItemCount();
+ }
+
+ public int getNavigationMode() {
+ return bar.getNavigationMode();
+ }
+
+ public int getSelectedNavigationIndex() {
+ return bar.getSelectedNavigationIndex();
+ }
+
+ public CharSequence getTitle() {
+ return bar.getTitle();
+ }
+
+ public void hide() {
+ bar.hide();
+ }
+
+ public boolean isShowing() {
+ return bar.isShowing();
+ }
+
+ public void setDisplayOptions(int options) {
+ bar.setDisplayOptions(options);
+ }
+
+ public void setDisplayOptions(int options, int mask) {
+ bar.setDisplayOptions(options, mask);
+ }
+
+ public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
+ bar.setListNavigationCallbacks(adapter, wrapOnNavigationCallback(callback));
+ }
+
+ public void setNavigationMode(int mode) {
+ bar.setNavigationMode(mode);
+ }
+
+ public void setSelectedNavigationItem(int position) {
+ bar.setSelectedNavigationItem(position);
+ }
+
+ public void setTitle(int resId) {
+ bar.setTitle(resId);
+ }
+
+ public void setTitle(CharSequence title) {
+ bar.setTitle(title);
+ }
+
+ public void show() {
+ bar.show();
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/compat/ActivityCompat.java b/term/src/main/java/jackpal/androidterm/compat/ActivityCompat.java
new file mode 100644
index 000000000..ab6e4f269
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/compat/ActivityCompat.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 Steven Luo
+ *
+ * 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 jackpal.androidterm.compat;
+
+import android.app.Activity;
+
+/**
+ * Compatibility class for android.app.Activity
+ */
+public class ActivityCompat {
+ private static class Api11OrLater {
+ public static void invalidateOptionsMenu(Activity activity) {
+ activity.invalidateOptionsMenu();
+ }
+
+ public static Object getActionBar(Activity activity) {
+ return activity.getActionBar();
+ }
+ }
+
+ public static void invalidateOptionsMenu(Activity activity) {
+ if (AndroidCompat.SDK >= 11) {
+ Api11OrLater.invalidateOptionsMenu(activity);
+ }
+ }
+
+ public static ActionBarCompat getActionBar(Activity activity) {
+ if (AndroidCompat.SDK < 11) {
+ return null;
+ }
+ return ActionBarCompat.wrap(Api11OrLater.getActionBar(activity));
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/compat/AlertDialogCompat.java b/term/src/main/java/jackpal/androidterm/compat/AlertDialogCompat.java
new file mode 100644
index 000000000..28cacf81e
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/compat/AlertDialogCompat.java
@@ -0,0 +1,89 @@
+package jackpal.androidterm.compat;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+
+public class AlertDialogCompat extends AlertDialog
+{
+ // API 11
+ public static int THEME_HOLO_TRADITIONAL= 1;
+ public static int THEME_HOLO_DARK= 2;
+ public static int THEME_HOLO_LIGHT= 3;
+ // API 14
+ public static int THEME_DEVICE_DEFAULT_DARK= 4;
+ public static int THEME_DEVICE_DEFAULT_LIGHT= 5;
+ ////////////////////////////////////////////////////////////
+ private AlertDialogCompat(Context context)
+ {
+ super(context);
+ }
+ private AlertDialogCompat(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener)
+ {
+ super(context, cancelable, cancelListener);
+ }
+ ////////////////////////////////////////////////////////////
+ private static class Api11OrLater extends AlertDialog
+ {
+ public Api11OrLater(Context context, int theme)
+ {
+ super(context, theme);
+ }
+ public Api11OrLater(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener)
+ {
+ super(context, cancelable, cancelListener);
+ }
+ }
+ ////////////////////////////////////////////////////////////
+ private static class Api14OrLater extends AlertDialog
+ {
+ public Api14OrLater(Context context, int theme)
+ {
+ super(context, theme);
+ }
+ public Api14OrLater(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener)
+ {
+ super(context, cancelable, cancelListener);
+ }
+ }
+ ////////////////////////////////////////////////////////////
+ public static AlertDialog newInstance(Context context)
+ {
+ return(new AlertDialogCompat(context));
+ }
+ ////////////////////////////////////////////////////////////
+ public static AlertDialog newInstance(Context context, int theme)
+ {
+ if(AndroidCompat.SDK >= 14)
+ {
+ return(new Api14OrLater(context, theme));
+ }
+ if(AndroidCompat.SDK >= 11)
+ {
+ return(new Api11OrLater(context, theme));
+ }
+ return(new AlertDialogCompat(context));
+ }
+ ////////////////////////////////////////////////////////////
+ public static AlertDialog newInstance(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener)
+ {
+ return(new AlertDialogCompat(context, cancelable, cancelListener));
+ }
+ ////////////////////////////////////////////////////////////
+
+ public static AlertDialog.Builder newInstanceBuilder(Context context, int theme) {
+ if (AndroidCompat.SDK >= 11) {
+ return new Api11OrLaterBuilder(context, theme);
+ } else {
+ return new AlertDialog.Builder(context);
+ }
+ }
+ private static class Api11OrLaterBuilder extends AlertDialog.Builder {
+ public Api11OrLaterBuilder(Context context) {
+ super(context);
+ }
+ public Api11OrLaterBuilder(Context context, int theme) {
+ super(context, theme);
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/compat/AndroidCompat.java b/term/src/main/java/jackpal/androidterm/compat/AndroidCompat.java
new file mode 100644
index 000000000..96355e0b2
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/compat/AndroidCompat.java
@@ -0,0 +1,47 @@
+package jackpal.androidterm.compat;
+
+/**
+ * The classes in this package take advantage of the fact that the VM does
+ * not attempt to load a class until it's accessed, and the verifier
+ * does not run until a class is loaded. By keeping the methods which
+ * are unavailable on older platforms in subclasses which are only ever
+ * accessed on platforms where they are available, we can preserve
+ * compatibility with older platforms without resorting to reflection.
+ *
+ * See http://developer.android.com/resources/articles/backward-compatibility.html
+ * and http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html
+ * for further discussion of this technique.
+ */
+
+public class AndroidCompat {
+ public final static int SDK = getSDK();
+
+ // The era of Holo Design
+ public final static boolean V11ToV20;
+
+ static {
+ V11ToV20 = (SDK >= 11) && (SDK <= 20);
+ }
+
+ private final static int getSDK() {
+ int result;
+ try {
+ result = AndroidLevel4PlusCompat.getSDKInt();
+ } catch (VerifyError e) {
+ // We must be at an SDK level less than 4.
+ try {
+ result = Integer.valueOf(android.os.Build.VERSION.SDK);
+ } catch (NumberFormatException e2) {
+ // Couldn't parse string, assume the worst.
+ result = 1;
+ }
+ }
+ return result;
+ }
+}
+
+class AndroidLevel4PlusCompat {
+ static int getSDKInt() {
+ return android.os.Build.VERSION.SDK_INT;
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/compat/Base64.java b/term/src/main/java/jackpal/androidterm/compat/Base64.java
new file mode 100644
index 000000000..13da4ba59
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/compat/Base64.java
@@ -0,0 +1,750 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.compat;
+
+/**
+ * Copied from
+ *
+ * https://android.googlesource.com/platform/frameworks/base/+/android-5.0.0_r6/core/java/android/util/Base64.java
+ *
+ * to provide a Base64 implementation on Android API < 8 (where Base64 is not
+ * a part of the public API).
+ */
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Utilities for encoding and decoding the Base64 representation of
+ * binary data. See RFCs 2045 and 3548.
+ */
+public class Base64 {
+ /**
+ * Default values for encoder/decoder flags.
+ */
+ public static final int DEFAULT = 0;
+
+ /**
+ * Encoder flag bit to omit the padding '=' characters at the end
+ * of the output (if any).
+ */
+ public static final int NO_PADDING = 1;
+
+ /**
+ * Encoder flag bit to omit all line terminators (i.e., the output
+ * will be on one long line).
+ */
+ public static final int NO_WRAP = 2;
+
+ /**
+ * Encoder flag bit to indicate lines should be terminated with a
+ * CRLF pair instead of just an LF. Has no effect if {@code
+ * NO_WRAP} is specified as well.
+ */
+ public static final int CRLF = 4;
+
+ /**
+ * Encoder/decoder flag bit to indicate using the "URL and
+ * filename safe" variant of Base64 (see RFC 3548 section 4) where
+ * {@code -} and {@code _} are used in place of {@code +} and
+ * {@code /}.
+ */
+ public static final int URL_SAFE = 8;
+
+ /**
+ * Flag to pass to {@link Base64OutputStream} to indicate that it
+ * should not close the output stream it is wrapping when it
+ * itself is closed.
+ */
+ public static final int NO_CLOSE = 16;
+
+ // --------------------------------------------------------
+ // shared code
+ // --------------------------------------------------------
+
+ /* package */ static abstract class Coder {
+ public byte[] output;
+ public int op;
+
+ /**
+ * Encode/decode another block of input data. this.output is
+ * provided by the caller, and must be big enough to hold all
+ * the coded data. On exit, this.opwill be set to the length
+ * of the coded data.
+ *
+ * @param finish true if this is the final call to process for
+ * this object. Will finalize the coder state and
+ * include any final bytes in the output.
+ *
+ * @return true if the input so far is good; false if some
+ * error has been detected in the input stream..
+ */
+ public abstract boolean process(byte[] input, int offset, int len, boolean finish);
+
+ /**
+ * @return the maximum number of bytes a call to process()
+ * could produce for the given number of input bytes. This may
+ * be an overestimate.
+ */
+ public abstract int maxOutputSize(int len);
+ }
+
+ // --------------------------------------------------------
+ // decoding
+ // --------------------------------------------------------
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ *
The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param str the input String to decode, which is converted to
+ * bytes using the default charset
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(String str, int flags) {
+ return decode(str.getBytes(), flags);
+ }
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ *
The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the input array to decode
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int flags) {
+ return decode(input, 0, input.length, flags);
+ }
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ *
The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the data to decode
+ * @param offset the position within the input array at which to start
+ * @param len the number of bytes of input to decode
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int offset, int len, int flags) {
+ // Allocate space for the most data the input could represent.
+ // (It could contain less if it contains whitespace, etc.)
+ Decoder decoder = new Decoder(flags, new byte[len*3/4]);
+
+ if (!decoder.process(input, offset, len, true)) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+
+ // Maybe we got lucky and allocated exactly enough output space.
+ if (decoder.op == decoder.output.length) {
+ return decoder.output;
+ }
+
+ // Need to shorten the array, so allocate a new one of the
+ // right size and copy.
+ byte[] temp = new byte[decoder.op];
+ System.arraycopy(decoder.output, 0, temp, 0, decoder.op);
+ return temp;
+ }
+
+ /* package */ static class Decoder extends Coder {
+ /**
+ * Lookup table for turning bytes into their position in the
+ * Base64 alphabet.
+ */
+ private static final int DECODE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ /**
+ * Decode lookup table for the "web safe" variant (RFC 3548
+ * sec. 4) where - and _ replace + and /.
+ */
+ private static final int DECODE_WEBSAFE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ /** Non-data values in the DECODE arrays. */
+ private static final int SKIP = -1;
+ private static final int EQUALS = -2;
+
+ /**
+ * States 0-3 are reading through the next input tuple.
+ * State 4 is having read one '=' and expecting exactly
+ * one more.
+ * State 5 is expecting no more data or padding characters
+ * in the input.
+ * State 6 is the error state; an error has been detected
+ * in the input and no future input can "fix" it.
+ */
+ private int state; // state number (0 to 6)
+ private int value;
+
+ final private int[] alphabet;
+
+ public Decoder(int flags, byte[] output) {
+ this.output = output;
+
+ alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE;
+ state = 0;
+ value = 0;
+ }
+
+ /**
+ * @return an overestimate for the number of bytes {@code
+ * len} bytes could decode to.
+ */
+ public int maxOutputSize(int len) {
+ return len * 3/4 + 10;
+ }
+
+ /**
+ * Decode another block of input data.
+ *
+ * @return true if the state machine is still healthy. false if
+ * bad base-64 data has been detected in the input stream.
+ */
+ public boolean process(byte[] input, int offset, int len, boolean finish) {
+ if (this.state == 6) return false;
+
+ int p = offset;
+ len += offset;
+
+ // Using local variables makes the decoder about 12%
+ // faster than if we manipulate the member variables in
+ // the loop. (Even alphabet makes a measurable
+ // difference, which is somewhat surprising to me since
+ // the member variable is final.)
+ int state = this.state;
+ int value = this.value;
+ int op = 0;
+ final byte[] output = this.output;
+ final int[] alphabet = this.alphabet;
+
+ while (p < len) {
+ // Try the fast path: we're starting a new tuple and the
+ // next four bytes of the input stream are all data
+ // bytes. This corresponds to going through states
+ // 0-1-2-3-0. We expect to use this method for most of
+ // the data.
+ //
+ // If any of the next four bytes of input are non-data
+ // (whitespace, etc.), value will end up negative. (All
+ // the non-data values in decode are small negative
+ // numbers, so shifting any of them up and or'ing them
+ // together will result in a value with its top bit set.)
+ //
+ // You can remove this whole block and the output should
+ // be the same, just slower.
+ if (state == 0) {
+ while (p+4 <= len &&
+ (value = ((alphabet[input[p] & 0xff] << 18) |
+ (alphabet[input[p+1] & 0xff] << 12) |
+ (alphabet[input[p+2] & 0xff] << 6) |
+ (alphabet[input[p+3] & 0xff]))) >= 0) {
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ p += 4;
+ }
+ if (p >= len) break;
+ }
+
+ // The fast path isn't available -- either we've read a
+ // partial tuple, or the next four input bytes aren't all
+ // data, or whatever. Fall back to the slower state
+ // machine implementation.
+
+ int d = alphabet[input[p++] & 0xff];
+
+ switch (state) {
+ case 0:
+ if (d >= 0) {
+ value = d;
+ ++state;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 1:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 2:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect exactly one more padding character.
+ output[op++] = (byte) (value >> 4);
+ state = 4;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 3:
+ if (d >= 0) {
+ // Emit the output triple and return to state 0.
+ value = (value << 6) | d;
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ state = 0;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect no further data or padding characters.
+ output[op+1] = (byte) (value >> 2);
+ output[op] = (byte) (value >> 10);
+ op += 2;
+ state = 5;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 4:
+ if (d == EQUALS) {
+ ++state;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 5:
+ if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+ }
+ }
+
+ if (!finish) {
+ // We're out of input, but a future call could provide
+ // more.
+ this.state = state;
+ this.value = value;
+ this.op = op;
+ return true;
+ }
+
+ // Done reading input. Now figure out where we are left in
+ // the state machine and finish up.
+
+ switch (state) {
+ case 0:
+ // Output length is a multiple of three. Fine.
+ break;
+ case 1:
+ // Read one extra input byte, which isn't enough to
+ // make another output byte. Illegal.
+ this.state = 6;
+ return false;
+ case 2:
+ // Read two extra input bytes, enough to emit 1 more
+ // output byte. Fine.
+ output[op++] = (byte) (value >> 4);
+ break;
+ case 3:
+ // Read three extra input bytes, enough to emit 2 more
+ // output bytes. Fine.
+ output[op++] = (byte) (value >> 10);
+ output[op++] = (byte) (value >> 2);
+ break;
+ case 4:
+ // Read one padding '=' when we expected 2. Illegal.
+ this.state = 6;
+ return false;
+ case 5:
+ // Read all the padding '='s we expected and no more.
+ // Fine.
+ break;
+ }
+
+ this.state = state;
+ this.op = op;
+ return true;
+ }
+ }
+
+ // --------------------------------------------------------
+ // encoding
+ // --------------------------------------------------------
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @param input the data to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static String encodeToString(byte[] input, int flags) {
+ try {
+ return new String(encode(input, flags), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ // US-ASCII is guaranteed to be available.
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * start
+ * @param len the number of bytes of input to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static String encodeToString(byte[] input, int offset, int len, int flags) {
+ try {
+ return new String(encode(input, offset, len, flags), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ // US-ASCII is guaranteed to be available.
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @param input the data to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static byte[] encode(byte[] input, int flags) {
+ return encode(input, 0, input.length, flags);
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * start
+ * @param len the number of bytes of input to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static byte[] encode(byte[] input, int offset, int len, int flags) {
+ Encoder encoder = new Encoder(flags, null);
+
+ // Compute the exact length of the array we will produce.
+ int output_len = len / 3 * 4;
+
+ // Account for the tail of the data and the padding bytes, if any.
+ if (encoder.do_padding) {
+ if (len % 3 > 0) {
+ output_len += 4;
+ }
+ } else {
+ switch (len % 3) {
+ case 0: break;
+ case 1: output_len += 2; break;
+ case 2: output_len += 3; break;
+ }
+ }
+
+ // Account for the newlines, if any.
+ if (encoder.do_newline && len > 0) {
+ output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) *
+ (encoder.do_cr ? 2 : 1);
+ }
+
+ encoder.output = new byte[output_len];
+ encoder.process(input, offset, len, true);
+
+ assert encoder.op == output_len;
+
+ return encoder.output;
+ }
+
+ /* package */ static class Encoder extends Coder {
+ /**
+ * Emit a new line every this many output tuples. Corresponds to
+ * a 76-character line length (the maximum allowable according to
+ * RFC 2045).
+ */
+ public static final int LINE_GROUPS = 19;
+
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
+ };
+
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE_WEBSAFE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
+ };
+
+ final private byte[] tail;
+ /* package */ int tailLen;
+ private int count;
+
+ final public boolean do_padding;
+ final public boolean do_newline;
+ final public boolean do_cr;
+ final private byte[] alphabet;
+
+ public Encoder(int flags, byte[] output) {
+ this.output = output;
+
+ do_padding = (flags & NO_PADDING) == 0;
+ do_newline = (flags & NO_WRAP) == 0;
+ do_cr = (flags & CRLF) != 0;
+ alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
+
+ tail = new byte[2];
+ tailLen = 0;
+
+ count = do_newline ? LINE_GROUPS : -1;
+ }
+
+ /**
+ * @return an overestimate for the number of bytes {@code
+ * len} bytes could encode to.
+ */
+ public int maxOutputSize(int len) {
+ return len * 8/5 + 10;
+ }
+
+ public boolean process(byte[] input, int offset, int len, boolean finish) {
+ // Using local variables makes the encoder about 9% faster.
+ final byte[] alphabet = this.alphabet;
+ final byte[] output = this.output;
+ int op = 0;
+ int count = this.count;
+
+ int p = offset;
+ len += offset;
+ int v = -1;
+
+ // First we need to concatenate the tail of the previous call
+ // with any input bytes available now and see if we can empty
+ // the tail.
+
+ switch (tailLen) {
+ case 0:
+ // There was no tail.
+ break;
+
+ case 1:
+ if (p+2 <= len) {
+ // A 1-byte tail with at least 2 bytes of
+ // input available now.
+ v = ((tail[0] & 0xff) << 16) |
+ ((input[p++] & 0xff) << 8) |
+ (input[p++] & 0xff);
+ tailLen = 0;
+ };
+ break;
+
+ case 2:
+ if (p+1 <= len) {
+ // A 2-byte tail with at least 1 byte of input.
+ v = ((tail[0] & 0xff) << 16) |
+ ((tail[1] & 0xff) << 8) |
+ (input[p++] & 0xff);
+ tailLen = 0;
+ }
+ break;
+ }
+
+ if (v != -1) {
+ output[op++] = alphabet[(v >> 18) & 0x3f];
+ output[op++] = alphabet[(v >> 12) & 0x3f];
+ output[op++] = alphabet[(v >> 6) & 0x3f];
+ output[op++] = alphabet[v & 0x3f];
+ if (--count == 0) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ count = LINE_GROUPS;
+ }
+ }
+
+ // At this point either there is no tail, or there are fewer
+ // than 3 bytes of input available.
+
+ // The main loop, turning 3 input bytes into 4 output bytes on
+ // each iteration.
+ while (p+3 <= len) {
+ v = ((input[p] & 0xff) << 16) |
+ ((input[p+1] & 0xff) << 8) |
+ (input[p+2] & 0xff);
+ output[op] = alphabet[(v >> 18) & 0x3f];
+ output[op+1] = alphabet[(v >> 12) & 0x3f];
+ output[op+2] = alphabet[(v >> 6) & 0x3f];
+ output[op+3] = alphabet[v & 0x3f];
+ p += 3;
+ op += 4;
+ if (--count == 0) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ count = LINE_GROUPS;
+ }
+ }
+
+ if (finish) {
+ // Finish up the tail of the input. Note that we need to
+ // consume any bytes in tail before any bytes
+ // remaining in input; there should be at most two bytes
+ // total.
+
+ if (p-tailLen == len-1) {
+ int t = 0;
+ v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4;
+ tailLen -= t;
+ output[op++] = alphabet[(v >> 6) & 0x3f];
+ output[op++] = alphabet[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (p-tailLen == len-2) {
+ int t = 0;
+ v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) |
+ (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2);
+ tailLen -= t;
+ output[op++] = alphabet[(v >> 12) & 0x3f];
+ output[op++] = alphabet[(v >> 6) & 0x3f];
+ output[op++] = alphabet[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (do_newline && op > 0 && count != LINE_GROUPS) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+
+ assert tailLen == 0;
+ assert p == len;
+ } else {
+ // Save the leftovers in tail to be consumed on the next
+ // call to encodeInternal.
+
+ if (p == len-1) {
+ tail[tailLen++] = input[p];
+ } else if (p == len-2) {
+ tail[tailLen++] = input[p];
+ tail[tailLen++] = input[p+1];
+ }
+ }
+
+ this.op = op;
+ this.count = count;
+
+ return true;
+ }
+ }
+
+ private Base64() { } // don't instantiate
+}
diff --git a/term/src/main/java/jackpal/androidterm/compat/FileCompat.java b/term/src/main/java/jackpal/androidterm/compat/FileCompat.java
new file mode 100644
index 000000000..33f874a0a
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/compat/FileCompat.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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 jackpal.androidterm.compat;
+
+import java.io.File;
+
+/**
+ * Compatibility class for java.io.File
+ */
+public class FileCompat {
+ private static class Api9OrLater {
+ public static boolean canExecute(File file) {
+ return file.canExecute();
+ }
+ }
+
+ private static class Api8OrEarlier {
+ static {
+ System.loadLibrary("jackpal-androidterm5");
+ }
+
+ public static boolean canExecute(File file) {
+ return testExecute(file.getAbsolutePath());
+ }
+
+ private static native boolean testExecute(String pathname);
+ }
+
+ public static boolean canExecute(File file) {
+ if (AndroidCompat.SDK < 9) {
+ return Api8OrEarlier.canExecute(file);
+ } else {
+ return Api9OrLater.canExecute(file);
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/compat/MenuItemCompat.java b/term/src/main/java/jackpal/androidterm/compat/MenuItemCompat.java
new file mode 100644
index 000000000..456fa798b
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/compat/MenuItemCompat.java
@@ -0,0 +1,25 @@
+package jackpal.androidterm.compat;
+
+import android.view.MenuItem;
+
+/**
+ * Definitions related to android.view.MenuItem
+ */
+public class MenuItemCompat {
+ public static final int SHOW_AS_ACTION_NEVER = 0;
+ public static final int SHOW_AS_ACTION_IF_ROOM = 1;
+ public static final int SHOW_AS_ACTION_ALWAYS = 2;
+ public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
+
+ private static class Api11OrLater {
+ public static void setShowAsAction(MenuItem item, int actionEnum) {
+ item.setShowAsAction(actionEnum);
+ }
+ }
+
+ public static void setShowAsAction(MenuItem item, int actionEnum) {
+ if (AndroidCompat.SDK >= 11) {
+ Api11OrLater.setShowAsAction(item, actionEnum);
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/compat/PRNGFixes.java b/term/src/main/java/jackpal/androidterm/compat/PRNGFixes.java
new file mode 100644
index 000000000..d313d7146
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/compat/PRNGFixes.java
@@ -0,0 +1,345 @@
+/*
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will Google be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, as long as the origin is not misrepresented.
+ */
+
+package jackpal.androidterm.compat;
+
+/**
+ * Copied from
+ *
+ * http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
+ *
+ * to work around entropy-quality problems with Android's SecureRandom
+ * implementations before 4.4.
+ */
+
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+import java.security.Security;
+
+/**
+ * Fixes for the output of the default PRNG having low entropy.
+ *
+ * The fixes need to be applied via {@link #apply()} before any use of Java
+ * Cryptography Architecture primitives. A good place to invoke them is in the
+ * application's {@code onCreate}.
+ */
+public final class PRNGFixes {
+
+ private static final int VERSION_CODE_JELLY_BEAN = 16;
+ private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
+ private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
+ getBuildFingerprintAndDeviceSerial();
+
+ /** Hidden constructor to prevent instantiation. */
+ private PRNGFixes() {}
+
+ /**
+ * Applies all fixes.
+ *
+ * @throws SecurityException if a fix is needed but could not be applied.
+ */
+ public static void apply() {
+ applyOpenSSLFix();
+ installLinuxPRNGSecureRandom();
+ }
+
+ /**
+ * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
+ * fix is not needed.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void applyOpenSSLFix() throws SecurityException {
+ if ((AndroidCompat.SDK < VERSION_CODE_JELLY_BEAN)
+ || (AndroidCompat.SDK > VERSION_CODE_JELLY_BEAN_MR2)) {
+ // No need to apply the fix
+ return;
+ }
+
+ try {
+ // Mix in the device- and invocation-specific seed.
+ Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_seed", byte[].class)
+ .invoke(null, generateSeed());
+
+ // Mix output of Linux PRNG into OpenSSL's PRNG
+ int bytesRead = (Integer) Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_load_file", String.class, long.class)
+ .invoke(null, "/dev/urandom", 1024);
+ if (bytesRead != 1024) {
+ throw new IOException(
+ "Unexpected number of bytes read from Linux PRNG: "
+ + bytesRead);
+ }
+ } catch (Exception e) {
+ throw new SecurityException("Failed to seed OpenSSL PRNG", e);
+ }
+ }
+
+ /**
+ * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
+ * default. Does nothing if the implementation is already the default or if
+ * there is not need to install the implementation.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void installLinuxPRNGSecureRandom()
+ throws SecurityException {
+ if (AndroidCompat.SDK > VERSION_CODE_JELLY_BEAN_MR2) {
+ // No need to apply the fix
+ return;
+ }
+
+ // Install a Linux PRNG-based SecureRandom implementation as the
+ // default, if not yet installed.
+ Provider[] secureRandomProviders =
+ Security.getProviders("SecureRandom.SHA1PRNG");
+ if ((secureRandomProviders == null)
+ || (secureRandomProviders.length < 1)
+ || (!LinuxPRNGSecureRandomProvider.class.equals(
+ secureRandomProviders[0].getClass()))) {
+ Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
+ }
+
+ // Assert that new SecureRandom() and
+ // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+ // by the Linux PRNG-based SecureRandom implementation.
+ SecureRandom rng1 = new SecureRandom();
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng1.getProvider().getClass())) {
+ throw new SecurityException(
+ "new SecureRandom() backed by wrong Provider: "
+ + rng1.getProvider().getClass());
+ }
+
+ SecureRandom rng2;
+ try {
+ rng2 = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecurityException("SHA1PRNG not available", e);
+ }
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng2.getProvider().getClass())) {
+ throw new SecurityException(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ + " Provider: " + rng2.getProvider().getClass());
+ }
+ }
+
+ /**
+ * {@code Provider} of {@code SecureRandom} engines which pass through
+ * all requests to the Linux PRNG.
+ */
+ private static class LinuxPRNGSecureRandomProvider extends Provider {
+
+ public LinuxPRNGSecureRandomProvider() {
+ super("LinuxPRNG",
+ 1.0,
+ "A Linux-specific random number provider that uses"
+ + " /dev/urandom");
+ // Although /dev/urandom is not a SHA-1 PRNG, some apps
+ // explicitly request a SHA1PRNG SecureRandom and we thus need to
+ // prevent them from getting the default implementation whose output
+ // may have low entropy.
+ put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
+ put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+ }
+ }
+
+ /**
+ * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
+ * ({@code /dev/urandom}).
+ */
+ public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
+
+ /*
+ * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
+ * are passed through to the Linux PRNG (/dev/urandom). Instances of
+ * this class seed themselves by mixing in the current time, PID, UID,
+ * build fingerprint, and hardware serial number (where available) into
+ * Linux PRNG.
+ *
+ * Concurrency: Read requests to the underlying Linux PRNG are
+ * serialized (on sLock) to ensure that multiple threads do not get
+ * duplicated PRNG output.
+ */
+
+ private static final File URANDOM_FILE = new File("/dev/urandom");
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Input stream for reading from Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static DataInputStream sUrandomIn;
+
+ /**
+ * Output stream for writing to Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static OutputStream sUrandomOut;
+
+ /**
+ * Whether this engine instance has been seeded. This is needed because
+ * each instance needs to seed itself if the client does not explicitly
+ * seed it.
+ */
+ private boolean mSeeded;
+
+ @Override
+ protected void engineSetSeed(byte[] bytes) {
+ try {
+ OutputStream out;
+ synchronized (sLock) {
+ out = getUrandomOutputStream();
+ }
+ out.write(bytes);
+ out.flush();
+ } catch (IOException e) {
+ // On a small fraction of devices /dev/urandom is not writable.
+ // Log and ignore.
+ Log.w(PRNGFixes.class.getSimpleName(),
+ "Failed to mix seed into " + URANDOM_FILE);
+ } finally {
+ mSeeded = true;
+ }
+ }
+
+ @Override
+ protected void engineNextBytes(byte[] bytes) {
+ if (!mSeeded) {
+ // Mix in the device- and invocation-specific seed.
+ engineSetSeed(generateSeed());
+ }
+
+ try {
+ DataInputStream in;
+ synchronized (sLock) {
+ in = getUrandomInputStream();
+ }
+ synchronized (in) {
+ in.readFully(bytes);
+ }
+ } catch (IOException e) {
+ throw new SecurityException(
+ "Failed to read from " + URANDOM_FILE, e);
+ }
+ }
+
+ @Override
+ protected byte[] engineGenerateSeed(int size) {
+ byte[] seed = new byte[size];
+ engineNextBytes(seed);
+ return seed;
+ }
+
+ private DataInputStream getUrandomInputStream() {
+ synchronized (sLock) {
+ if (sUrandomIn == null) {
+ // NOTE: Consider inserting a BufferedInputStream between
+ // DataInputStream and FileInputStream if you need higher
+ // PRNG output performance and can live with future PRNG
+ // output being pulled into this process prematurely.
+ try {
+ sUrandomIn = new DataInputStream(
+ new FileInputStream(URANDOM_FILE));
+ } catch (IOException e) {
+ throw new SecurityException("Failed to open "
+ + URANDOM_FILE + " for reading", e);
+ }
+ }
+ return sUrandomIn;
+ }
+ }
+
+ private OutputStream getUrandomOutputStream() throws IOException {
+ synchronized (sLock) {
+ if (sUrandomOut == null) {
+ sUrandomOut = new FileOutputStream(URANDOM_FILE);
+ }
+ return sUrandomOut;
+ }
+ }
+ }
+
+ /**
+ * Generates a device- and invocation-specific seed to be mixed into the
+ * Linux PRNG.
+ */
+ private static byte[] generateSeed() {
+ try {
+ ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
+ DataOutputStream seedBufferOut =
+ new DataOutputStream(seedBuffer);
+ seedBufferOut.writeLong(System.currentTimeMillis());
+ seedBufferOut.writeLong(System.nanoTime());
+ seedBufferOut.writeInt(Process.myPid());
+ seedBufferOut.writeInt(Process.myUid());
+ seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
+ seedBufferOut.close();
+ return seedBuffer.toByteArray();
+ } catch (IOException e) {
+ throw new SecurityException("Failed to generate seed", e);
+ }
+ }
+
+ /**
+ * Gets the hardware serial number of this device.
+ *
+ * @return serial number or {@code null} if not available.
+ */
+ private static String getDeviceSerialNumber() {
+ // We're using the Reflection API because Build.SERIAL is only available
+ // since API Level 9 (Gingerbread, Android 2.3).
+ try {
+ return (String) Build.class.getField("SERIAL").get(null);
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+
+ private static byte[] getBuildFingerprintAndDeviceSerial() {
+ StringBuilder result = new StringBuilder();
+ String fingerprint = Build.FINGERPRINT;
+ if (fingerprint != null) {
+ result.append(fingerprint);
+ }
+ String serial = getDeviceSerialNumber();
+ if (serial != null) {
+ result.append(serial);
+ }
+ try {
+ return result.toString().getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not supported");
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/compat/ServiceForegroundCompat.java b/term/src/main/java/jackpal/androidterm/compat/ServiceForegroundCompat.java
new file mode 100644
index 000000000..25f34dc94
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/compat/ServiceForegroundCompat.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 Steven Luo
+ * Copyright (C) 2011 Android Open Source Project
+ *
+ * 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 jackpal.androidterm.compat;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+import android.app.Service;
+import android.util.Log;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+
+/* Provide startForeground() and stopForeground() compatibility, using the
+ current interfaces where available and the deprecated setForeground()
+ interface where necessary
+ The idea for the implementation comes from an example in the documentation of
+ android.app.Service */
+public class ServiceForegroundCompat {
+ private static Class>[] mSetForegroundSig = new Class[] {
+ boolean.class };
+ private static Class>[] mStartForegroundSig = new Class[] {
+ int.class, Notification.class };
+ private static Class>[] mStopForegroundSig = new Class[] {
+ boolean.class };
+
+ private Service service;
+ private NotificationManager mNM;
+ private Method mSetForeground;
+ private Method mStartForeground;
+ private Method mStopForeground;
+ private int notifyId;
+
+ private void invokeMethod(Object receiver, Method method, Object... args) {
+ try {
+ method.invoke(receiver, args);
+ } catch (IllegalAccessException e) {
+ // Shouldn't happen, but we have to catch this
+ Log.w("ServiceCompat", "Unable to invoke method", e);
+ } catch (InvocationTargetException e) {
+ /* The methods we call don't throw exceptions -- in general,
+ we should throw e.getCause() */
+ Log.w("ServiceCompat", "Method threw exception", e.getCause());
+ }
+ }
+
+ public void startForeground(int id, Notification notification) {
+ if (mStartForeground != null) {
+ invokeMethod(service, mStartForeground, id, notification);
+ return;
+ }
+
+ invokeMethod(service, mSetForeground, Boolean.TRUE);
+ mNM.notify(id, notification);
+ notifyId = id;
+ }
+
+ public void stopForeground(boolean removeNotify) {
+ if (mStopForeground != null) {
+ invokeMethod(service, mStopForeground, removeNotify);
+ return;
+ }
+
+ if (removeNotify) {
+ mNM.cancel(notifyId);
+ }
+ invokeMethod(service, mSetForeground, Boolean.FALSE);
+ }
+
+ public ServiceForegroundCompat(Service service) {
+ this.service = service;
+ mNM = (NotificationManager)service.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ Class> clazz = service.getClass();
+
+ try {
+ mStartForeground = clazz.getMethod("startForeground", mStartForegroundSig);
+ mStopForeground = clazz.getMethod("stopForeground", mStopForegroundSig);
+ } catch (NoSuchMethodException e) {
+ mStartForeground = mStopForeground = null;
+ }
+
+ try {
+ mSetForeground = clazz.getMethod("setForeground", mSetForegroundSig);
+ } catch (NoSuchMethodException e) {
+ mSetForeground = null;
+ }
+
+ if (mStartForeground == null && mSetForeground == null) {
+ throw new IllegalStateException("Neither startForeground() or setForeground() present!");
+ }
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/shortcuts/AddShortcut.java b/term/src/main/java/jackpal/androidterm/shortcuts/AddShortcut.java
new file mode 100644
index 000000000..f92cdb32c
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/shortcuts/AddShortcut.java
@@ -0,0 +1,312 @@
+//From the desk of Frank P. Westlake; public domain.
+package jackpal.androidterm.shortcuts;
+
+import android.content. Context;
+import android.content. DialogInterface;
+import android.content. Intent;
+import android.content. SharedPreferences;
+import android.graphics. Typeface;
+import android.net. Uri;
+import android.os. Bundle;
+import android.os. Environment;
+import android.preference. PreferenceManager;
+import android.util. Log;
+import android.view. Gravity;
+import android.view. View;
+import android.view. View.OnFocusChangeListener;
+import android.widget. Button;
+import android.widget. ImageView;
+import android.widget. LinearLayout;
+import android.widget. ScrollView;
+import android.widget. TextView;
+import android.widget. EditText;
+import jackpal.androidterm.R;
+import jackpal.androidterm.RemoteInterface;
+import jackpal.androidterm.RunShortcut;
+import jackpal.androidterm.TermDebug;
+import jackpal.androidterm.compat.AlertDialogCompat;
+import jackpal.androidterm.compat.PRNGFixes;
+import jackpal.androidterm.util.ShortcutEncryption;
+
+import java.io. File;
+import java.security. GeneralSecurityException;
+
+public class AddShortcut
+ extends android.app.Activity
+{
+ private final int OP_MAKE_SHORTCUT= 1;
+ private final Context context= this;
+ private SharedPreferences SP;
+ private int ix= 0;
+ private final int PATH= ix++
+ , ARGS= ix++
+ , NAME= ix++;
+ private final EditText et[]= new EditText[5];
+ private String path;
+ private String name="";
+ private String iconText[]= {"", null};
+
+ //////////////////////////////////////////////////////////////////////
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ SP=PreferenceManager.getDefaultSharedPreferences(context);
+ String action=getIntent().getAction();
+ if(action!=null && action.equals("android.intent.action.CREATE_SHORTCUT")) makeShortcut();
+ else finish();
+ }
+ //////////////////////////////////////////////////////////////////////
+ void makeShortcut()
+ {
+ if(path==null) path="";
+ final AlertDialogCompat.Builder alert =
+ AlertDialogCompat.newInstanceBuilder(context, AlertDialogCompat.THEME_HOLO_DARK);
+ LinearLayout lv=new LinearLayout(context);
+ lv.setOrientation(LinearLayout.VERTICAL);
+ for(int i=0, n=et.length; i>(24-i*8))&0xFF;
+ TextView lt=new TextView(context);
+ lt.setText(context.getString(R.string.colorvalue_label_lock_button_column));//"LOCK");
+ lt.setPadding(lt.getPaddingLeft(), lt.getPaddingTop(), 5, lt.getPaddingBottom());
+ lt.setGravity(Gravity.RIGHT);
+ value=new EditText(context);
+ value.setText(imgtext);
+ value.setSingleLine(false);
+ value.setGravity(Gravity.CENTER);
+ value.setTextColor((Integer)imgview.getTag());
+ value.setBackgroundColor((0xFF<<24)|0x007799);
+ LinearLayout vh=new LinearLayout(context);
+ vh.setOrientation(LinearLayout.HORIZONTAL);
+ vh.setGravity(Gravity.CENTER_HORIZONTAL);
+ vh.addView(value);
+ value.setHint(context.getString(R.string.colorvalue_icon_text_entry_hint));//"Enter icon text");
+ lv.addView(vh);
+ lv.addView(lt);
+ final SeekBar sb[]= new SeekBar[arraySizes+1];
+ final CheckBox lk[]= new CheckBox[arraySizes];
+ final TextView hexWindow[]= new TextView[arraySizes];
+ for(int i=0; i=0; n-=4) s+=HEX.charAt((k>>n)&0xF);
+ tv.setText(s);
+ }
+ ////////////////////////////////////////////////////////////
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
+ {
+ int view=(Integer)buttonView.getTag();
+ locks[view]=isChecked;
+ barLock=false;
+ for(int i=0; i cachedFileView;
+ private HashMap cachedDirectoryView;
+ private HashMap cachedDividerView;
+ private int countFileView;
+ private int countDirectoryView;
+ private int countDividerView;
+ private LinearLayout contentView;
+ private LinearLayout titleView;
+ private LinearLayout pathEntryView;
+
+ ////////////////////////////////////////////////////////////
+ public void onCreate(android.os.Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setTitle(getString(R.string.fsnavigator_title));//"File Selector");
+ SP=PreferenceManager.getDefaultSharedPreferences(context);
+ theme=SP.getInt("theme", theme);
+ setTheme(theme);
+ getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
+ Intent intent= getIntent();
+ extSdCardFile=Environment.getExternalStorageDirectory();
+ extSdCard=getCanonicalPath(extSdCardFile);
+ Uri uri=intent.getData();
+ String path=uri==null?null:uri.getPath();
+ if(null == path || null==(chdir(path))) chdir(extSdCard);
+ if(intent.hasExtra("title")) setTitle(intent.getStringExtra("title"));
+
+ titleView= directoryEntry("..");
+ pathEntryView= fileEntry(null);
+ contentView= makeContentView();
+ cachedDirectoryView= new HashMap();
+ cachedFileView= new HashMap();
+ cachedDividerView= new HashMap();
+ }
+ ////////////////////////////////////////////////////////////
+ public void onPause()
+ {
+ super.onPause();
+ doPause();
+ }
+ ////////////////////////////////////////////////////////////
+ private void doPause()
+ {
+ SP.edit().putString("lastDirectory", getCanonicalPath(cd)).commit();
+ }
+ ////////////////////////////////////////////////////////////
+ public void onResume()
+ {
+ super.onResume();
+ doResume();
+ }
+ ////////////////////////////////////////////////////////////
+ private void doResume()
+ {
+ makeView();
+ }
+ ////////////////////////////////////////////////////////////
+ private void swapTheme()
+ {
+ switch(theme)
+ {
+ case android.R.style.Theme: theme=android.R.style.Theme_Light; break;
+ case android.R.style.Theme_Light: theme=android.R.style.Theme; break;
+ default: return;
+ }
+ SP.edit().putInt("theme", theme).commit();
+ startActivityForResult(getIntent().addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), -1);
+ finish();
+ }
+ ////////////////////////////////////////////////////////////
+ private String ifAvailable(String goTo)
+ {
+ if(goTo.startsWith(extSdCard))
+ {
+ String s=Environment.getExternalStorageState();
+ if(s.equals(Environment.MEDIA_MOUNTED)
+ || s.equals(Environment.MEDIA_MOUNTED_READ_ONLY)
+ )
+ {
+ return(goTo);
+ }
+ toast(getString(R.string.fsnavigator_no_external_storage), 1);//"External storage not available", 1);
+ return(extSdCard);
+ }
+ return(goTo);
+ }
+ ////////////////////////////////////////////////////////////
+ private File chdir(File file)
+ {
+ String path=ifAvailable(getCanonicalPath(file));
+ System.setProperty("user.dir", path);
+ return(cd=new File(path));
+ }
+ private File chdir(String path)
+ {
+ return(chdir(new File(path)));
+ }
+ ////////////////////////////////////////////////////////////
+ private TextView entryDividerH()
+ {
+ TextView tv;
+ if(countDividerView stringSortComparator=new java.util.Comparator()
+ {
+ public int compare(String a, String b) {return(a.toLowerCase().compareTo(b.toLowerCase()));}
+ };
+ //////////////////////////////////////////////////////////////////////
+ String getCanonicalPath(String path)
+ {
+ return(getCanonicalPath(new File(path)));
+ }
+ String getCanonicalPath(File file)
+ {
+ try{return(file.getCanonicalPath());}catch(IOException e){return(file.getPath());}
+ }
+ //////////////////////////////////////////////////////////////////////
+ public boolean onCreateOptionsMenu(Menu menu)
+// public boolean onPrepareOptionsMenu(Menu menu)
+ {
+ super.onCreateOptionsMenu(menu);
+// super.onPrepareOptionsMenu(menu); menu.clear();
+ menu.add(0, ACTION_THEME_SWAP, 0, getString(R.string.fsnavigator_change_theme));//"Change theme");
+ return(true);
+ }
+ //////////////////////////////////////////////////////////////////////
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ super.onOptionsItemSelected(item);
+ return(doOptionsItem(item.getItemId()));
+ }
+ //////////////////////////////////////////////////////////////////////
+ private boolean doOptionsItem(int itemId)
+ {
+ switch(itemId)
+ {
+ case ACTION_THEME_SWAP: swapTheme(); return(true);
+ }
+ return(false);
+ }
+ //////////////////////////////////////////////////////////////////////
+// private void toast(final String message){toast(message, 0);}
+ private void toast(final String message, final int duration)
+ {
+ runOnUiThread(
+ new Runnable()
+ {
+ public void run()
+ {
+ Toast.makeText(context, message, duration == 0 ?Toast.LENGTH_SHORT: Toast.LENGTH_LONG).show();
+ }
+ }
+ );
+ }
+ //////////////////////////////////////////////////////////////////////
+}
diff --git a/term/src/main/java/jackpal/androidterm/shortcuts/TextIcon.java b/term/src/main/java/jackpal/androidterm/shortcuts/TextIcon.java
new file mode 100644
index 000000000..dca0616ca
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/shortcuts/TextIcon.java
@@ -0,0 +1,68 @@
+package jackpal.androidterm.shortcuts;
+
+import android.graphics. Bitmap;
+import android.graphics. Bitmap.Config;
+import android.graphics. Canvas;
+import android.graphics. Paint;
+import android.graphics. Paint.Align;
+import android.graphics. Rect;
+import android.util. FloatMath;
+import java.lang. Float;
+
+public class TextIcon
+{
+ ////////////////////////////////////////////////////////////
+ public static Bitmap getTextIcon(String text, int color, int width, int height)
+ {
+ text= text. trim();
+ String lines[]= text.split("\\s*\n\\s*");
+ int nLines= lines.length;
+ Rect R= new Rect();
+ Paint p= new Paint(Paint.ANTI_ALIAS_FLAG);
+ p. setShadowLayer(2, 10, 10, 0xFF000000);
+ p. setColor(color);
+ p. setSubpixelText(true);
+ p. setTextSize(256);
+ p. setTextAlign(Align.CENTER);
+ float HH[]= new float[nLines];
+ float H= 0f;
+ float W= 0f;
+ for(int i=0; i1) h+=0.1f*h; // Add space between lines.
+ HH[i]= h;
+ H+= h;
+ if(w>W) W=w;
+ }
+ float f= ((float)width)*H/((float)height);
+ int hBitmap= (int)H;
+ int wBitmap= (int)W;
+ if(W
+{
+ LinkedList callbacks = new LinkedList();
+ LinkedList titleChangedListeners = new LinkedList();
+ UpdateCallback mTitleChangedListener = new UpdateCallback() {
+ public void onUpdate() {
+ notifyTitleChanged();
+ }
+ };
+
+ public SessionList() {
+ super();
+ }
+
+ public SessionList(int capacity) {
+ super(capacity);
+ }
+
+ public void addCallback(UpdateCallback callback) {
+ callbacks.add(callback);
+ callback.onUpdate();
+ }
+
+ public boolean removeCallback(UpdateCallback callback) {
+ return callbacks.remove(callback);
+ }
+
+ private void notifyChange() {
+ for (UpdateCallback callback : callbacks) {
+ callback.onUpdate();
+ }
+ }
+
+ public void addTitleChangedListener(UpdateCallback listener) {
+ titleChangedListeners.add(listener);
+ listener.onUpdate();
+ }
+
+ public boolean removeTitleChangedListener(UpdateCallback listener) {
+ return titleChangedListeners.remove(listener);
+ }
+
+ private void notifyTitleChanged() {
+ for (UpdateCallback listener : titleChangedListeners) {
+ listener.onUpdate();
+ }
+ }
+
+ @Override
+ public boolean add(TermSession object) {
+ boolean result = super.add(object);
+ object.setTitleChangedListener(mTitleChangedListener);
+ notifyChange();
+ return result;
+ }
+
+ @Override
+ public void add(int index, TermSession object) {
+ super.add(index, object);
+ object.setTitleChangedListener(mTitleChangedListener);
+ notifyChange();
+ }
+
+ @Override
+ public boolean addAll(Collection extends TermSession> collection) {
+ boolean result = super.addAll(collection);
+ for (TermSession session : collection) {
+ session.setTitleChangedListener(mTitleChangedListener);
+ }
+ notifyChange();
+ return result;
+ }
+
+ @Override
+ public boolean addAll(int index, Collection extends TermSession> collection) {
+ boolean result = super.addAll(index, collection);
+ for (TermSession session : collection) {
+ session.setTitleChangedListener(mTitleChangedListener);
+ }
+ notifyChange();
+ return result;
+ }
+
+ @Override
+ public void clear() {
+ for (TermSession session : this) {
+ session.setTitleChangedListener(null);
+ }
+ super.clear();
+ notifyChange();
+ }
+
+ @Override
+ public TermSession remove(int index) {
+ TermSession object = super.remove(index);
+ if (object != null) {
+ object.setTitleChangedListener(null);
+ notifyChange();
+ }
+ return object;
+ }
+
+ @Override
+ public boolean remove(Object object) {
+ boolean result = super.remove(object);
+ if (result && object instanceof TermSession) {
+ ((TermSession) object).setTitleChangedListener(null);
+ notifyChange();
+ }
+ return result;
+ }
+
+ @Override
+ public TermSession set(int index, TermSession object) {
+ TermSession old = super.set(index, object);
+ object.setTitleChangedListener(mTitleChangedListener);
+ if (old != null) {
+ old.setTitleChangedListener(null);
+ }
+ notifyChange();
+ return old;
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/util/ShortcutEncryption.java b/term/src/main/java/jackpal/androidterm/util/ShortcutEncryption.java
new file mode 100644
index 000000000..a899e7938
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/util/ShortcutEncryption.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2015 Steven Luo
+ *
+ * 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 jackpal.androidterm.util;
+
+import jackpal.androidterm.compat.Base64;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.regex.Pattern;
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Implementation of a simple authenticated encryption scheme suitable for
+ * TEA shortcuts.
+ *
+ * The goals of the encryption are as follows:
+ *
+ * (1) An unauthorized actor must not be able to create a valid text with
+ * contents of his choice;
+ * (2) An unauthorized actor must not be able to modify an existing text to
+ * change its contents in any way;
+ * (3) An unauthorized actor must not be able to discover the contents of
+ * an existing text.
+ *
+ * Conditions (1) and (2) ensure that an attacker cannot send commands of his
+ * choosing to TEA via the shortcut mechanism, while condition (3) ensures that
+ * an attacker cannot learn what commands are being sent via shortcuts even if
+ * he can read saved shortcuts or sniff Android intents.
+ *
+ * We ensure these conditions using two cryptographic building blocks:
+ *
+ * * a symmetric cipher (currently AES in CBC mode using PKCS#5 padding),
+ * which prevents someone without the encryption key from reading the
+ * contents of the shortcut; and
+ * * a message authentication code (currently HMAC-SHA256), which proves that
+ * the shortcut was created by someone with the MAC key.
+ *
+ * The security of these depends on the security of the keys, which must be
+ * kept secret. In this application, the keys are randomly generated and stored
+ * in the application's private shared preferences.
+ *
+ * The encrypted string output by this scheme is of the form:
+ *
+ * mac + ":" + iv + ":" cipherText
+ *
+ * where:
+ *
+ * * cipherText is the Base64-encoded result of encrypting the data
+ * using the encryption key;
+ * * iv is a Base64-encoded, non-secret random number used as an
+ * initialization vector for the encryption algorithm;
+ * * mac is the Base64 encoding of MAC(MAC-key, iv + ":" + cipherText).
+ */
+public final class ShortcutEncryption {
+ public static final String ENC_ALGORITHM = "AES";
+ public static final String ENC_SYSTEM = ENC_ALGORITHM + "/CBC/PKCS5Padding";
+ public static final int ENC_BLOCKSIZE = 16;
+ public static final String MAC_ALGORITHM = "HmacSHA256";
+ public static final int KEYLEN = 128;
+ public static final int BASE64_DFLAGS = Base64.DEFAULT;
+ public static final int BASE64_EFLAGS = Base64.NO_PADDING | Base64.NO_WRAP;
+
+ private static final String SHORTCUT_KEYS_PREF = "shortcut_keys";
+
+ private static final Pattern COLON = Pattern.compile(":");
+
+ public static final class Keys {
+ private final SecretKey encKey;
+ private final SecretKey macKey;
+
+ public Keys(SecretKey encKey, SecretKey macKey) {
+ this.encKey = encKey;
+ this.macKey = macKey;
+ }
+
+ public SecretKey getEncKey() {
+ return encKey;
+ }
+
+ public SecretKey getMacKey() {
+ return macKey;
+ }
+
+ /**
+ * Outputs the keys as a string of the form
+ *
+ * encKey + ":" + macKey
+ *
+ * where encKey and macKey are the Base64-encoded encryption and MAC
+ * keys.
+ */
+ public String encode() {
+ return encodeToBase64(encKey.getEncoded()) + ":" + encodeToBase64(macKey.getEncoded());
+ }
+
+ /**
+ * Creates a new Keys object by decoding a string of the form output
+ * from encode().
+ */
+ public static Keys decode(String encodedKeys) {
+ String[] keys = COLON.split(encodedKeys);
+ if (keys.length != 2) {
+ throw new IllegalArgumentException("Invalid encoded keys!");
+ }
+
+ SecretKey encKey = new SecretKeySpec(decodeBase64(keys[0]), ENC_ALGORITHM);
+ SecretKey macKey = new SecretKeySpec(decodeBase64(keys[1]), MAC_ALGORITHM);
+ return new Keys(encKey, macKey);
+ }
+ }
+
+ /**
+ * Retrieves the shortcut encryption keys from preferences.
+ */
+ public static Keys getKeys(Context ctx) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
+ String keyEnc = prefs.getString(SHORTCUT_KEYS_PREF, null);
+ if (keyEnc == null) {
+ return null;
+ }
+
+ try {
+ return Keys.decode(keyEnc);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Saves shortcut encryption keys to preferences.
+ */
+ public static void saveKeys(Context ctx, Keys keys) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
+
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString(SHORTCUT_KEYS_PREF, keys.encode());
+ edit.commit();
+ }
+
+ /**
+ * Generates new secret keys suitable for the encryption scheme described
+ * above.
+ *
+ * @throws GeneralSecurityException if an error occurs during key generation.
+ */
+ public static Keys generateKeys() throws GeneralSecurityException {
+ KeyGenerator gen = KeyGenerator.getInstance(ENC_ALGORITHM);
+ gen.init(KEYLEN);
+ SecretKey encKey = gen.generateKey();
+
+ /* XXX: It's probably unnecessary to create a different keygen for the
+ * MAC, but JCA's API design suggests we should just in case ... */
+ gen = KeyGenerator.getInstance(MAC_ALGORITHM);
+ gen.init(KEYLEN);
+ SecretKey macKey = gen.generateKey();
+
+ return new Keys(encKey, macKey);
+ }
+
+ /**
+ * Decrypts a string encrypted using this algorithm and verifies that the
+ * contents have not been tampered with.
+ *
+ * @param encrypted The string to decrypt, in the format described above.
+ * @param keys The keys to verify and decrypt with.
+ * @return The decrypted data.
+ *
+ * @throws GeneralSecurityException if the data is invalid, verification fails, or an error occurs during decryption.
+ */
+ public static String decrypt(String encrypted, Keys keys) throws GeneralSecurityException {
+ Cipher cipher = Cipher.getInstance(ENC_SYSTEM);
+ String[] data = COLON.split(encrypted);
+ if (data.length != 3) {
+ throw new GeneralSecurityException("Invalid encrypted data!");
+ }
+ String mac = data[0];
+ String iv = data[1];
+ String cipherText = data[2];
+
+ // Verify that the ciphertext and IV haven't been tampered with first
+ String dataToAuth = iv + ":" + cipherText;
+ if (!computeMac(dataToAuth, keys.getMacKey()).equals(mac)) {
+ throw new GeneralSecurityException("Incorrect MAC!");
+ }
+
+ // Decrypt the ciphertext
+ byte[] ivBytes = decodeBase64(iv);
+ cipher.init(Cipher.DECRYPT_MODE, keys.getEncKey(), new IvParameterSpec(ivBytes));
+ byte[] bytes = cipher.doFinal(decodeBase64(cipherText));
+
+ // Decode the plaintext bytes into a String
+ CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
+ decoder.onMalformedInput(CodingErrorAction.REPORT);
+ decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
+ /*
+ * We are coding UTF-8 (guaranteed to be the default charset on
+ * Android) to Java chars (UTF-16, 2 bytes per char). For valid UTF-8
+ * sequences, then:
+ * 1 byte in UTF-8 (US-ASCII) -> 1 char in UTF-16
+ * 2-3 bytes in UTF-8 (BMP) -> 1 char in UTF-16
+ * 4 bytes in UTF-8 (non-BMP) -> 2 chars in UTF-16 (surrogate pair)
+ * The decoded output is therefore guaranteed to fit into a char
+ * array the same length as the input byte array.
+ */
+ CharBuffer out = CharBuffer.allocate(bytes.length);
+ CoderResult result = decoder.decode(ByteBuffer.wrap(bytes), out, true);
+ if (result.isError()) {
+ /* The input was supposed to be the result of encrypting a String,
+ * so something is very wrong if it cannot be decoded into one! */
+ throw new GeneralSecurityException("Corrupt decrypted data!");
+ }
+ decoder.flush(out);
+ return out.flip().toString();
+ }
+
+ /**
+ * Encrypts and authenticates a string using the algorithm described above.
+ *
+ * @param data The string containing the data to encrypt.
+ * @param keys The keys to encrypt and authenticate with.
+ * @return The encrypted data.
+ *
+ * @throws GeneralSecurityException if an error occurs during encryption.
+ */
+ public static String encrypt(String data, Keys keys) throws GeneralSecurityException {
+ Cipher cipher = Cipher.getInstance(ENC_SYSTEM);
+
+ // Generate a random IV
+ SecureRandom rng = new SecureRandom();
+ byte[] ivBytes = new byte[ENC_BLOCKSIZE];
+ rng.nextBytes(ivBytes);
+ String iv = encodeToBase64(ivBytes);
+
+ // Encrypt
+ cipher.init(Cipher.ENCRYPT_MODE, keys.getEncKey(), new IvParameterSpec(ivBytes));
+ byte[] bytes = data.getBytes();
+ String cipherText = encodeToBase64(cipher.doFinal(bytes));
+
+ // Calculate the MAC for the ciphertext and IV
+ String dataToAuth = iv + ":" + cipherText;
+ String mac = computeMac(dataToAuth, keys.getMacKey());
+
+ return mac + ":" + dataToAuth;
+ }
+
+ /**
+ * Computes the Base64-encoded Message Authentication Code for the
+ * data using the provided key.
+ *
+ * @throws GeneralSecurityException if an error occurs during MAC computation.
+ */
+ private static String computeMac(String data, SecretKey key) throws GeneralSecurityException {
+ Mac mac = Mac.getInstance(MAC_ALGORITHM);
+ mac.init(key);
+ byte[] macBytes = mac.doFinal(data.getBytes());
+ return encodeToBase64(macBytes);
+ }
+
+ /**
+ * Encodes binary data to Base64 using the settings specified by
+ * BASE64_EFLAGS.
+ *
+ * @return A String with the Base64-encoded data.
+ */
+ private static String encodeToBase64(byte[] data) {
+ return Base64.encodeToString(data, BASE64_EFLAGS);
+ }
+
+ /**
+ * Decodes Base64-encoded binary data using the settings specified by
+ * BASE64_DFLAGS.
+ *
+ * @param data A String with the Base64-encoded data.
+ * @return A newly-allocated byte[] array with the decoded data.
+ */
+ private static byte[] decodeBase64(String data) {
+ return Base64.decode(data, BASE64_DFLAGS);
+ }
+
+ // Prevent instantiation
+ private ShortcutEncryption() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/term/src/main/java/jackpal/androidterm/util/TermSettings.java b/term/src/main/java/jackpal/androidterm/util/TermSettings.java
new file mode 100644
index 000000000..8ef5bf267
--- /dev/null
+++ b/term/src/main/java/jackpal/androidterm/util/TermSettings.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.util;
+
+import jackpal.androidterm.R;
+import jackpal.androidterm.compat.AndroidCompat;
+
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.view.KeyEvent;
+
+/**
+ * Terminal emulator settings
+ */
+public class TermSettings {
+ private SharedPreferences mPrefs;
+
+ private int mStatusBar;
+ private int mActionBarMode;
+ private int mOrientation;
+ private int mCursorStyle;
+ private int mCursorBlink;
+ private int mFontSize;
+ private int mColorId;
+ private boolean mUTF8ByDefault;
+ private int mBackKeyAction;
+ private int mControlKeyId;
+ private int mFnKeyId;
+ private int mUseCookedIME;
+ private String mShell;
+ private String mFailsafeShell;
+ private String mInitialCommand;
+ private String mTermType;
+ private boolean mCloseOnExit;
+ private boolean mVerifyPath;
+ private boolean mDoPathExtensions;
+ private boolean mAllowPathPrepend;
+ private String mHomePath;
+
+ private String mPrependPath = null;
+ private String mAppendPath = null;
+
+ private boolean mAltSendsEsc;
+
+ private boolean mMouseTracking;
+
+ private boolean mUseKeyboardShortcuts;
+
+ private static final String STATUSBAR_KEY = "statusbar";
+ private static final String ACTIONBAR_KEY = "actionbar";
+ private static final String ORIENTATION_KEY = "orientation";
+ private static final String FONTSIZE_KEY = "fontsize";
+ private static final String COLOR_KEY = "color";
+ private static final String UTF8_KEY = "utf8_by_default";
+ private static final String BACKACTION_KEY = "backaction";
+ private static final String CONTROLKEY_KEY = "controlkey";
+ private static final String FNKEY_KEY = "fnkey";
+ private static final String IME_KEY = "ime";
+ private static final String SHELL_KEY = "shell";
+ private static final String INITIALCOMMAND_KEY = "initialcommand";
+ private static final String TERMTYPE_KEY = "termtype";
+ private static final String CLOSEONEXIT_KEY = "close_window_on_process_exit";
+ private static final String VERIFYPATH_KEY = "verify_path";
+ private static final String PATHEXTENSIONS_KEY = "do_path_extensions";
+ private static final String PATHPREPEND_KEY = "allow_prepend_path";
+ private static final String HOMEPATH_KEY = "home_path";
+ private static final String ALT_SENDS_ESC = "alt_sends_esc";
+ private static final String MOUSE_TRACKING = "mouse_tracking";
+ private static final String USE_KEYBOARD_SHORTCUTS = "use_keyboard_shortcuts";
+
+ public static final int WHITE = 0xffffffff;
+ public static final int BLACK = 0xff000000;
+ public static final int BLUE = 0xff344ebd;
+ public static final int GREEN = 0xff00ff00;
+ public static final int AMBER = 0xffffb651;
+ public static final int RED = 0xffff0113;
+ public static final int HOLO_BLUE = 0xff33b5e5;
+ public static final int SOLARIZED_FG = 0xff657b83;
+ public static final int SOLARIZED_BG = 0xfffdf6e3;
+ public static final int SOLARIZED_DARK_FG = 0xff839496;
+ public static final int SOLARIZED_DARK_BG = 0xff002b36;
+ public static final int LINUX_CONSOLE_WHITE = 0xffaaaaaa;
+
+ // foreground color, background color
+ public static final int[][] COLOR_SCHEMES = {
+ {BLACK, WHITE},
+ {WHITE, BLACK},
+ {WHITE, BLUE},
+ {GREEN, BLACK},
+ {AMBER, BLACK},
+ {RED, BLACK},
+ {HOLO_BLUE, BLACK},
+ {SOLARIZED_FG, SOLARIZED_BG},
+ {SOLARIZED_DARK_FG, SOLARIZED_DARK_BG},
+ {LINUX_CONSOLE_WHITE, BLACK}
+ };
+
+ public static final int ACTION_BAR_MODE_NONE = 0;
+ public static final int ACTION_BAR_MODE_ALWAYS_VISIBLE = 1;
+ public static final int ACTION_BAR_MODE_HIDES = 2;
+ private static final int ACTION_BAR_MODE_MAX = 2;
+
+ public static final int ORIENTATION_UNSPECIFIED = 0;
+ public static final int ORIENTATION_LANDSCAPE = 1;
+ public static final int ORIENTATION_PORTRAIT = 2;
+
+ /** An integer not in the range of real key codes. */
+ public static final int KEYCODE_NONE = -1;
+
+ public static final int CONTROL_KEY_ID_NONE = 7;
+ public static final int[] CONTROL_KEY_SCHEMES = {
+ KeyEvent.KEYCODE_DPAD_CENTER,
+ KeyEvent.KEYCODE_AT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_ALT_RIGHT,
+ KeyEvent.KEYCODE_VOLUME_UP,
+ KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_CAMERA,
+ KEYCODE_NONE
+ };
+
+ public static final int FN_KEY_ID_NONE = 7;
+ public static final int[] FN_KEY_SCHEMES = {
+ KeyEvent.KEYCODE_DPAD_CENTER,
+ KeyEvent.KEYCODE_AT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_ALT_RIGHT,
+ KeyEvent.KEYCODE_VOLUME_UP,
+ KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_CAMERA,
+ KEYCODE_NONE
+ };
+
+ public static final int BACK_KEY_STOPS_SERVICE = 0;
+ public static final int BACK_KEY_CLOSES_WINDOW = 1;
+ public static final int BACK_KEY_CLOSES_ACTIVITY = 2;
+ public static final int BACK_KEY_SENDS_ESC = 3;
+ public static final int BACK_KEY_SENDS_TAB = 4;
+ private static final int BACK_KEY_MAX = 4;
+
+ public TermSettings(Resources res, SharedPreferences prefs) {
+ readDefaultPrefs(res);
+ readPrefs(prefs);
+ }
+
+ private void readDefaultPrefs(Resources res) {
+ mStatusBar = Integer.parseInt(res.getString(R.string.pref_statusbar_default));
+ mActionBarMode = res.getInteger(R.integer.pref_actionbar_default);
+ mOrientation = res.getInteger(R.integer.pref_orientation_default);
+ mCursorStyle = Integer.parseInt(res.getString(R.string.pref_cursorstyle_default));
+ mCursorBlink = Integer.parseInt(res.getString(R.string.pref_cursorblink_default));
+ mFontSize = Integer.parseInt(res.getString(R.string.pref_fontsize_default));
+ mColorId = Integer.parseInt(res.getString(R.string.pref_color_default));
+ mUTF8ByDefault = res.getBoolean(R.bool.pref_utf8_by_default_default);
+ mBackKeyAction = Integer.parseInt(res.getString(R.string.pref_backaction_default));
+ mControlKeyId = Integer.parseInt(res.getString(R.string.pref_controlkey_default));
+ mFnKeyId = Integer.parseInt(res.getString(R.string.pref_fnkey_default));
+ mUseCookedIME = Integer.parseInt(res.getString(R.string.pref_ime_default));
+ mFailsafeShell = res.getString(R.string.pref_shell_default);
+ mShell = mFailsafeShell;
+ mInitialCommand = res.getString(R.string.pref_initialcommand_default);
+ mTermType = res.getString(R.string.pref_termtype_default);
+ mCloseOnExit = res.getBoolean(R.bool.pref_close_window_on_process_exit_default);
+ mVerifyPath = res.getBoolean(R.bool.pref_verify_path_default);
+ mDoPathExtensions = res.getBoolean(R.bool.pref_do_path_extensions_default);
+ mAllowPathPrepend = res.getBoolean(R.bool.pref_allow_prepend_path_default);
+ // the mHomePath default is set dynamically in readPrefs()
+ mAltSendsEsc = res.getBoolean(R.bool.pref_alt_sends_esc_default);
+ mMouseTracking = res.getBoolean(R.bool.pref_mouse_tracking_default);
+ mUseKeyboardShortcuts = res.getBoolean(R.bool.pref_use_keyboard_shortcuts_default);
+ }
+
+ public void readPrefs(SharedPreferences prefs) {
+ mPrefs = prefs;
+ mStatusBar = readIntPref(STATUSBAR_KEY, mStatusBar, 1);
+ mActionBarMode = readIntPref(ACTIONBAR_KEY, mActionBarMode, ACTION_BAR_MODE_MAX);
+ mOrientation = readIntPref(ORIENTATION_KEY, mOrientation, 2);
+ // mCursorStyle = readIntPref(CURSORSTYLE_KEY, mCursorStyle, 2);
+ // mCursorBlink = readIntPref(CURSORBLINK_KEY, mCursorBlink, 1);
+ mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 288);
+ mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1);
+ mUTF8ByDefault = readBooleanPref(UTF8_KEY, mUTF8ByDefault);
+ mBackKeyAction = readIntPref(BACKACTION_KEY, mBackKeyAction, BACK_KEY_MAX);
+ mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId,
+ CONTROL_KEY_SCHEMES.length - 1);
+ mFnKeyId = readIntPref(FNKEY_KEY, mFnKeyId,
+ FN_KEY_SCHEMES.length - 1);
+ mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1);
+ mShell = readStringPref(SHELL_KEY, mShell);
+ mInitialCommand = readStringPref(INITIALCOMMAND_KEY, mInitialCommand);
+ mTermType = readStringPref(TERMTYPE_KEY, mTermType);
+ mCloseOnExit = readBooleanPref(CLOSEONEXIT_KEY, mCloseOnExit);
+ mVerifyPath = readBooleanPref(VERIFYPATH_KEY, mVerifyPath);
+ mDoPathExtensions = readBooleanPref(PATHEXTENSIONS_KEY, mDoPathExtensions);
+ mAllowPathPrepend = readBooleanPref(PATHPREPEND_KEY, mAllowPathPrepend);
+ mHomePath = readStringPref(HOMEPATH_KEY, mHomePath);
+ mAltSendsEsc = readBooleanPref(ALT_SENDS_ESC, mAltSendsEsc);
+ mMouseTracking = readBooleanPref(MOUSE_TRACKING, mMouseTracking);
+ mUseKeyboardShortcuts = readBooleanPref(USE_KEYBOARD_SHORTCUTS,
+ mUseKeyboardShortcuts);
+ mPrefs = null; // we leak a Context if we hold on to this
+ }
+
+ private int readIntPref(String key, int defaultValue, int maxValue) {
+ int val;
+ try {
+ val = Integer.parseInt(
+ mPrefs.getString(key, Integer.toString(defaultValue)));
+ } catch (NumberFormatException e) {
+ val = defaultValue;
+ }
+ val = Math.max(0, Math.min(val, maxValue));
+ return val;
+ }
+
+ private String readStringPref(String key, String defaultValue) {
+ return mPrefs.getString(key, defaultValue);
+ }
+
+ private boolean readBooleanPref(String key, boolean defaultValue) {
+ return mPrefs.getBoolean(key, defaultValue);
+ }
+
+ public boolean showStatusBar() {
+ return (mStatusBar != 0);
+ }
+
+ public int actionBarMode() {
+ return mActionBarMode;
+ }
+
+ public int getScreenOrientation() {
+ return mOrientation;
+ }
+
+ public int getCursorStyle() {
+ return mCursorStyle;
+ }
+
+ public int getCursorBlink() {
+ return mCursorBlink;
+ }
+
+ public int getFontSize() {
+ return mFontSize;
+ }
+
+ public int[] getColorScheme() {
+ return COLOR_SCHEMES[mColorId];
+ }
+
+ public boolean defaultToUTF8Mode() {
+ return mUTF8ByDefault;
+ }
+
+ public int getBackKeyAction() {
+ return mBackKeyAction;
+ }
+
+ public boolean backKeySendsCharacter() {
+ return mBackKeyAction >= BACK_KEY_SENDS_ESC;
+ }
+
+ public boolean getAltSendsEscFlag() {
+ return mAltSendsEsc;
+ }
+
+ public boolean getMouseTrackingFlag() {
+ return mMouseTracking;
+ }
+
+ public boolean getUseKeyboardShortcutsFlag() {
+ return mUseKeyboardShortcuts;
+ }
+
+ public int getBackKeyCharacter() {
+ switch (mBackKeyAction) {
+ case BACK_KEY_SENDS_ESC: return 27;
+ case BACK_KEY_SENDS_TAB: return 9;
+ default: return 0;
+ }
+ }
+
+ public int getControlKeyId() {
+ return mControlKeyId;
+ }
+
+ public int getFnKeyId() {
+ return mFnKeyId;
+ }
+
+ public int getControlKeyCode() {
+ return CONTROL_KEY_SCHEMES[mControlKeyId];
+ }
+
+ public int getFnKeyCode() {
+ return FN_KEY_SCHEMES[mFnKeyId];
+ }
+
+ public boolean useCookedIME() {
+ return (mUseCookedIME != 0);
+ }
+
+ public String getShell() {
+ return mShell;
+ }
+
+ public String getFailsafeShell() {
+ return mFailsafeShell;
+ }
+
+ public String getInitialCommand() {
+ return mInitialCommand;
+ }
+
+ public String getTermType() {
+ return mTermType;
+ }
+
+ public boolean closeWindowOnProcessExit() {
+ return mCloseOnExit;
+ }
+
+ public boolean verifyPath() {
+ return mVerifyPath;
+ }
+
+ public boolean doPathExtensions() {
+ return mDoPathExtensions;
+ }
+
+ public boolean allowPathPrepend() {
+ return mAllowPathPrepend;
+ }
+
+ public void setPrependPath(String prependPath) {
+ mPrependPath = prependPath;
+ }
+
+ public String getPrependPath() {
+ return mPrependPath;
+ }
+
+ public void setAppendPath(String appendPath) {
+ mAppendPath = appendPath;
+ }
+
+ public String getAppendPath() {
+ return mAppendPath;
+ }
+
+ public void setHomePath(String homePath) {
+ mHomePath = homePath;
+ }
+
+ public String getHomePath() {
+ return mHomePath;
+ }
+}
diff --git a/term/src/main/jni/common.cpp b/term/src/main/jni/common.cpp
new file mode 100644
index 000000000..380c5c278
--- /dev/null
+++ b/term/src/main/jni/common.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "common.h"
+#include "termExec.h"
+#include "fileCompat.h"
+
+#define LOG_TAG "libjackpal-androidterm"
+
+/*
+ * Register several native methods for one class.
+ */
+int registerNativeMethods(JNIEnv* env, const char* className,
+ JNINativeMethod* gMethods, int numMethods)
+{
+ jclass clazz;
+
+ clazz = env->FindClass(className);
+ if (clazz == NULL) {
+ LOGE("Native registration unable to find class '%s'", className);
+ return JNI_FALSE;
+ }
+ if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+ LOGE("RegisterNatives failed for '%s'", className);
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * This is called by the VM when the shared library is first loaded.
+ */
+
+typedef union {
+ JNIEnv* env;
+ void* venv;
+} UnionJNIEnvToVoid;
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ UnionJNIEnvToVoid uenv;
+ uenv.venv = NULL;
+ jint result = -1;
+ JNIEnv* env = NULL;
+
+ LOGI("JNI_OnLoad");
+
+ if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
+ LOGE("ERROR: GetEnv failed");
+ goto bail;
+ }
+ env = uenv.env;
+
+ if (init_Exec(env) != JNI_TRUE) {
+ LOGE("ERROR: init of Exec failed");
+ goto bail;
+ }
+
+ if (init_FileCompat(env) != JNI_TRUE) {
+ LOGE("ERROR: init of Exec failed");
+ goto bail;
+ }
+
+ result = JNI_VERSION_1_4;
+
+bail:
+ return result;
+}
diff --git a/term/src/main/jni/common.h b/term/src/main/jni/common.h
new file mode 100644
index 000000000..c98d7e82c
--- /dev/null
+++ b/term/src/main/jni/common.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _COMMON_H
+#define _COMMON_H 1
+
+#include
+
+#include "jni.h"
+#include
+
+#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
+#define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0)
+#define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
+
+int registerNativeMethods(JNIEnv* env, const char* className,
+ JNINativeMethod* gMethods, int numMethods);
+
+#endif
diff --git a/term/src/main/jni/fileCompat.cpp b/term/src/main/jni/fileCompat.cpp
new file mode 100644
index 000000000..32ef11c17
--- /dev/null
+++ b/term/src/main/jni/fileCompat.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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.
+ */
+
+#include "common.h"
+
+#define LOG_TAG "FileCompat"
+
+#include
+
+#include "fileCompat.h"
+
+static jboolean testExecute(JNIEnv *env, jobject clazz, jstring jPathString)
+{
+ const char *pathname = NULL;
+ int result;
+
+ /* XXX We should convert CESU-8 to UTF-8 to deal with potential non-BMP
+ chars in pathname */
+ pathname = env->GetStringUTFChars(jPathString, NULL);
+
+ result = access(pathname, X_OK);
+
+ env->ReleaseStringUTFChars(jPathString, pathname);
+ return (result == 0);
+}
+
+static const char *classPathName = "jackpal/androidterm/compat/FileCompat$Api8OrEarlier";
+static JNINativeMethod method_table[] = {
+ { "testExecute", "(Ljava/lang/String;)Z", (void *) testExecute },
+};
+
+int init_FileCompat(JNIEnv *env) {
+ if (!registerNativeMethods(env, classPathName, method_table,
+ sizeof(method_table) / sizeof(method_table[0]))) {
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
diff --git a/term/src/main/jni/fileCompat.h b/term/src/main/jni/fileCompat.h
new file mode 100644
index 000000000..12264c502
--- /dev/null
+++ b/term/src/main/jni/fileCompat.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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.
+ */
+
+#ifndef _FILECOMPAT_H
+#define _FILECOMPAT_H 1
+
+#include "jni.h"
+
+int init_FileCompat(JNIEnv *env);
+
+#endif /* !defined(_FILECOMPAT_H) */
diff --git a/term/src/main/jni/termExec.cpp b/term/src/main/jni/termExec.cpp
new file mode 100644
index 000000000..e02ec299e
--- /dev/null
+++ b/term/src/main/jni/termExec.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007, 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "common.h"
+
+#define LOG_TAG "Exec"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "termExec.h"
+
+static void android_os_Exec_setPtyWindowSize(JNIEnv *env, jobject clazz,
+ jint fd, jint row, jint col, jint xpixel, jint ypixel)
+{
+ struct winsize sz;
+
+ sz.ws_row = row;
+ sz.ws_col = col;
+ sz.ws_xpixel = xpixel;
+ sz.ws_ypixel = ypixel;
+
+ // TODO: handle the situation, when the file descriptor is incompatible with TIOCSWINSZ (e.g. not from /dev/ptmx)
+ if (ioctl(fd, TIOCSWINSZ, &sz) == -1)
+ env->ThrowNew(env->FindClass("java/io/IOException"), "Failed to issue TIOCSWINSZ ioctl");
+}
+
+// tcgetattr /tcsetattr are not part of Bionic at API level 4. Here's a compatible version.
+
+static __inline__ int my_tcgetattr(int fd, struct termios *s)
+{
+ return ioctl(fd, TCGETS, s);
+}
+
+static __inline__ int my_tcsetattr(int fd, const struct termios *s)
+{
+ return ioctl(fd, TCSETS, (void *)s);
+}
+
+static void android_os_Exec_setPtyUTF8Mode(JNIEnv *env, jobject clazz, jint fd, jboolean utf8Mode)
+{
+ struct termios tios;
+
+ if (my_tcgetattr(fd, &tios) != 0)
+ env->ThrowNew(env->FindClass("java/io/IOException"), "Failed to get terminal attributes");
+
+ if (utf8Mode) {
+ tios.c_iflag |= IUTF8;
+ } else {
+ tios.c_iflag &= ~IUTF8;
+ }
+
+ if (my_tcsetattr(fd, &tios) != 0)
+ env->ThrowNew(env->FindClass("java/io/IOException"), "Failed to set terminal UTF-8 mode");
+}
+
+static const char *classPathName = "jackpal/androidterm/Exec";
+static JNINativeMethod method_table[] = {
+ { "setPtyWindowSizeInternal", "(IIIII)V",
+ (void*) android_os_Exec_setPtyWindowSize},
+ { "setPtyUTF8ModeInternal", "(IZ)V",
+ (void*) android_os_Exec_setPtyUTF8Mode}
+};
+
+int init_Exec(JNIEnv *env) {
+ if (!registerNativeMethods(env, classPathName, method_table,
+ sizeof(method_table) / sizeof(method_table[0]))) {
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
diff --git a/term/src/main/jni/termExec.h b/term/src/main/jni/termExec.h
new file mode 100644
index 000000000..23d2f3c41
--- /dev/null
+++ b/term/src/main/jni/termExec.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2012 Steven Luo
+ *
+ * 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.
+ */
+
+#ifndef _TERMEXEC_H
+#define _TERMEXEC_H 1
+
+#include "jni.h"
+
+int init_Exec(JNIEnv *env);
+
+#endif /* !defined(_TERMEXEC_H) */
diff --git a/term/src/main/res/drawable-hdpi-v11/ic_menu_add.png b/term/src/main/res/drawable-hdpi-v11/ic_menu_add.png
new file mode 100644
index 000000000..444e8a5ee
Binary files /dev/null and b/term/src/main/res/drawable-hdpi-v11/ic_menu_add.png differ
diff --git a/term/src/main/res/drawable-hdpi-v11/ic_menu_back.png b/term/src/main/res/drawable-hdpi-v11/ic_menu_back.png
new file mode 100644
index 000000000..661a4ae92
Binary files /dev/null and b/term/src/main/res/drawable-hdpi-v11/ic_menu_back.png differ
diff --git a/term/src/main/res/drawable-hdpi-v11/ic_menu_close_clear_cancel.png b/term/src/main/res/drawable-hdpi-v11/ic_menu_close_clear_cancel.png
new file mode 100644
index 000000000..778c7f081
Binary files /dev/null and b/term/src/main/res/drawable-hdpi-v11/ic_menu_close_clear_cancel.png differ
diff --git a/term/src/main/res/drawable-hdpi-v11/ic_menu_forward.png b/term/src/main/res/drawable-hdpi-v11/ic_menu_forward.png
new file mode 100644
index 000000000..b1b0c89d5
Binary files /dev/null and b/term/src/main/res/drawable-hdpi-v11/ic_menu_forward.png differ
diff --git a/term/src/main/res/drawable-hdpi-v11/ic_menu_preferences.png b/term/src/main/res/drawable-hdpi-v11/ic_menu_preferences.png
new file mode 100644
index 000000000..5321f8285
Binary files /dev/null and b/term/src/main/res/drawable-hdpi-v11/ic_menu_preferences.png differ
diff --git a/term/src/main/res/drawable-hdpi-v11/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-hdpi-v11/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..17a215b0b
Binary files /dev/null and b/term/src/main/res/drawable-hdpi-v11/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-hdpi-v9/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-hdpi-v9/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..6bd458c68
Binary files /dev/null and b/term/src/main/res/drawable-hdpi-v9/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-hdpi/btn_close_window.png b/term/src/main/res/drawable-hdpi/btn_close_window.png
new file mode 100644
index 000000000..12e5c5522
Binary files /dev/null and b/term/src/main/res/drawable-hdpi/btn_close_window.png differ
diff --git a/term/src/main/res/drawable-hdpi/ic_launcher.png b/term/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..734d771cf
Binary files /dev/null and b/term/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/term/src/main/res/drawable-hdpi/ic_menu_add.png b/term/src/main/res/drawable-hdpi/ic_menu_add.png
new file mode 100644
index 000000000..7b0dfc5e1
Binary files /dev/null and b/term/src/main/res/drawable-hdpi/ic_menu_add.png differ
diff --git a/term/src/main/res/drawable-hdpi/ic_menu_back.png b/term/src/main/res/drawable-hdpi/ic_menu_back.png
new file mode 100644
index 000000000..7abf81973
Binary files /dev/null and b/term/src/main/res/drawable-hdpi/ic_menu_back.png differ
diff --git a/term/src/main/res/drawable-hdpi/ic_menu_close_clear_cancel.png b/term/src/main/res/drawable-hdpi/ic_menu_close_clear_cancel.png
new file mode 100644
index 000000000..4683f6193
Binary files /dev/null and b/term/src/main/res/drawable-hdpi/ic_menu_close_clear_cancel.png differ
diff --git a/term/src/main/res/drawable-hdpi/ic_menu_forward.png b/term/src/main/res/drawable-hdpi/ic_menu_forward.png
new file mode 100644
index 000000000..6b1804f46
Binary files /dev/null and b/term/src/main/res/drawable-hdpi/ic_menu_forward.png differ
diff --git a/term/src/main/res/drawable-hdpi/ic_menu_preferences.png b/term/src/main/res/drawable-hdpi/ic_menu_preferences.png
new file mode 100644
index 000000000..039c72174
Binary files /dev/null and b/term/src/main/res/drawable-hdpi/ic_menu_preferences.png differ
diff --git a/term/src/main/res/drawable-hdpi/ic_menu_windows.png b/term/src/main/res/drawable-hdpi/ic_menu_windows.png
new file mode 100644
index 000000000..0e80009cb
Binary files /dev/null and b/term/src/main/res/drawable-hdpi/ic_menu_windows.png differ
diff --git a/term/src/main/res/drawable-hdpi/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-hdpi/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..b8f6665cb
Binary files /dev/null and b/term/src/main/res/drawable-hdpi/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-ldpi-v11/ic_menu_add.png b/term/src/main/res/drawable-ldpi-v11/ic_menu_add.png
new file mode 100644
index 000000000..89620af8c
Binary files /dev/null and b/term/src/main/res/drawable-ldpi-v11/ic_menu_add.png differ
diff --git a/term/src/main/res/drawable-ldpi-v11/ic_menu_back.png b/term/src/main/res/drawable-ldpi-v11/ic_menu_back.png
new file mode 100644
index 000000000..71eb533a7
Binary files /dev/null and b/term/src/main/res/drawable-ldpi-v11/ic_menu_back.png differ
diff --git a/term/src/main/res/drawable-ldpi-v11/ic_menu_close_clear_cancel.png b/term/src/main/res/drawable-ldpi-v11/ic_menu_close_clear_cancel.png
new file mode 100644
index 000000000..760b9254d
Binary files /dev/null and b/term/src/main/res/drawable-ldpi-v11/ic_menu_close_clear_cancel.png differ
diff --git a/term/src/main/res/drawable-ldpi-v11/ic_menu_forward.png b/term/src/main/res/drawable-ldpi-v11/ic_menu_forward.png
new file mode 100644
index 000000000..554cfb7b7
Binary files /dev/null and b/term/src/main/res/drawable-ldpi-v11/ic_menu_forward.png differ
diff --git a/term/src/main/res/drawable-ldpi-v11/ic_menu_preferences.png b/term/src/main/res/drawable-ldpi-v11/ic_menu_preferences.png
new file mode 100644
index 000000000..efc2f3e45
Binary files /dev/null and b/term/src/main/res/drawable-ldpi-v11/ic_menu_preferences.png differ
diff --git a/term/src/main/res/drawable-ldpi-v11/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-ldpi-v11/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..07769a9e1
Binary files /dev/null and b/term/src/main/res/drawable-ldpi-v11/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-ldpi-v9/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-ldpi-v9/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..f9f416e30
Binary files /dev/null and b/term/src/main/res/drawable-ldpi-v9/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-ldpi/ic_launcher.png b/term/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 000000000..ec8a6ed10
Binary files /dev/null and b/term/src/main/res/drawable-ldpi/ic_launcher.png differ
diff --git a/term/src/main/res/drawable-ldpi/ic_menu_add.png b/term/src/main/res/drawable-ldpi/ic_menu_add.png
new file mode 100644
index 000000000..89620af8c
Binary files /dev/null and b/term/src/main/res/drawable-ldpi/ic_menu_add.png differ
diff --git a/term/src/main/res/drawable-ldpi/ic_menu_close_clear_cancel.png b/term/src/main/res/drawable-ldpi/ic_menu_close_clear_cancel.png
new file mode 100644
index 000000000..760b9254d
Binary files /dev/null and b/term/src/main/res/drawable-ldpi/ic_menu_close_clear_cancel.png differ
diff --git a/term/src/main/res/drawable-ldpi/ic_menu_preferences.png b/term/src/main/res/drawable-ldpi/ic_menu_preferences.png
new file mode 100644
index 000000000..efc2f3e45
Binary files /dev/null and b/term/src/main/res/drawable-ldpi/ic_menu_preferences.png differ
diff --git a/term/src/main/res/drawable-ldpi/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-ldpi/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..356832d08
Binary files /dev/null and b/term/src/main/res/drawable-ldpi/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-mdpi-v11/ic_menu_add.png b/term/src/main/res/drawable-mdpi-v11/ic_menu_add.png
new file mode 100644
index 000000000..361c7c460
Binary files /dev/null and b/term/src/main/res/drawable-mdpi-v11/ic_menu_add.png differ
diff --git a/term/src/main/res/drawable-mdpi-v11/ic_menu_back.png b/term/src/main/res/drawable-mdpi-v11/ic_menu_back.png
new file mode 100644
index 000000000..bb6924552
Binary files /dev/null and b/term/src/main/res/drawable-mdpi-v11/ic_menu_back.png differ
diff --git a/term/src/main/res/drawable-mdpi-v11/ic_menu_close_clear_cancel.png b/term/src/main/res/drawable-mdpi-v11/ic_menu_close_clear_cancel.png
new file mode 100644
index 000000000..1161a7ccb
Binary files /dev/null and b/term/src/main/res/drawable-mdpi-v11/ic_menu_close_clear_cancel.png differ
diff --git a/term/src/main/res/drawable-mdpi-v11/ic_menu_forward.png b/term/src/main/res/drawable-mdpi-v11/ic_menu_forward.png
new file mode 100644
index 000000000..4a0b6ef68
Binary files /dev/null and b/term/src/main/res/drawable-mdpi-v11/ic_menu_forward.png differ
diff --git a/term/src/main/res/drawable-mdpi-v11/ic_menu_preferences.png b/term/src/main/res/drawable-mdpi-v11/ic_menu_preferences.png
new file mode 100644
index 000000000..ccc50e66e
Binary files /dev/null and b/term/src/main/res/drawable-mdpi-v11/ic_menu_preferences.png differ
diff --git a/term/src/main/res/drawable-mdpi-v11/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-mdpi-v11/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..4f7b0dfc9
Binary files /dev/null and b/term/src/main/res/drawable-mdpi-v11/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-mdpi-v9/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-mdpi-v9/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..0fe44c4d1
Binary files /dev/null and b/term/src/main/res/drawable-mdpi-v9/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-mdpi/btn_close_window.png b/term/src/main/res/drawable-mdpi/btn_close_window.png
new file mode 100644
index 000000000..53f878beb
Binary files /dev/null and b/term/src/main/res/drawable-mdpi/btn_close_window.png differ
diff --git a/term/src/main/res/drawable-mdpi/ic_launcher.png b/term/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..4d8141fb6
Binary files /dev/null and b/term/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/term/src/main/res/drawable-mdpi/ic_menu_add.png b/term/src/main/res/drawable-mdpi/ic_menu_add.png
new file mode 100644
index 000000000..57a4099bb
Binary files /dev/null and b/term/src/main/res/drawable-mdpi/ic_menu_add.png differ
diff --git a/term/src/main/res/drawable-mdpi/ic_menu_back.png b/term/src/main/res/drawable-mdpi/ic_menu_back.png
new file mode 100644
index 000000000..91fa3e74a
Binary files /dev/null and b/term/src/main/res/drawable-mdpi/ic_menu_back.png differ
diff --git a/term/src/main/res/drawable-mdpi/ic_menu_close_clear_cancel.png b/term/src/main/res/drawable-mdpi/ic_menu_close_clear_cancel.png
new file mode 100644
index 000000000..04a76b589
Binary files /dev/null and b/term/src/main/res/drawable-mdpi/ic_menu_close_clear_cancel.png differ
diff --git a/term/src/main/res/drawable-mdpi/ic_menu_forward.png b/term/src/main/res/drawable-mdpi/ic_menu_forward.png
new file mode 100644
index 000000000..632e4f6db
Binary files /dev/null and b/term/src/main/res/drawable-mdpi/ic_menu_forward.png differ
diff --git a/term/src/main/res/drawable-mdpi/ic_menu_preferences.png b/term/src/main/res/drawable-mdpi/ic_menu_preferences.png
new file mode 100644
index 000000000..2f34c7695
Binary files /dev/null and b/term/src/main/res/drawable-mdpi/ic_menu_preferences.png differ
diff --git a/term/src/main/res/drawable-mdpi/ic_menu_windows.png b/term/src/main/res/drawable-mdpi/ic_menu_windows.png
new file mode 100644
index 000000000..c33de2b04
Binary files /dev/null and b/term/src/main/res/drawable-mdpi/ic_menu_windows.png differ
diff --git a/term/src/main/res/drawable-mdpi/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-mdpi/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..635c6fe83
Binary files /dev/null and b/term/src/main/res/drawable-mdpi/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-xhdpi-v11/ic_menu_add.png b/term/src/main/res/drawable-xhdpi-v11/ic_menu_add.png
new file mode 100644
index 000000000..7d498a96e
Binary files /dev/null and b/term/src/main/res/drawable-xhdpi-v11/ic_menu_add.png differ
diff --git a/term/src/main/res/drawable-xhdpi-v11/ic_menu_back.png b/term/src/main/res/drawable-xhdpi-v11/ic_menu_back.png
new file mode 100644
index 000000000..8ac4f6498
Binary files /dev/null and b/term/src/main/res/drawable-xhdpi-v11/ic_menu_back.png differ
diff --git a/term/src/main/res/drawable-xhdpi-v11/ic_menu_close_clear_cancel.png b/term/src/main/res/drawable-xhdpi-v11/ic_menu_close_clear_cancel.png
new file mode 100644
index 000000000..d743d752b
Binary files /dev/null and b/term/src/main/res/drawable-xhdpi-v11/ic_menu_close_clear_cancel.png differ
diff --git a/term/src/main/res/drawable-xhdpi-v11/ic_menu_forward.png b/term/src/main/res/drawable-xhdpi-v11/ic_menu_forward.png
new file mode 100644
index 000000000..6463e7a48
Binary files /dev/null and b/term/src/main/res/drawable-xhdpi-v11/ic_menu_forward.png differ
diff --git a/term/src/main/res/drawable-xhdpi-v11/ic_menu_preferences.png b/term/src/main/res/drawable-xhdpi-v11/ic_menu_preferences.png
new file mode 100644
index 000000000..02cfbad0b
Binary files /dev/null and b/term/src/main/res/drawable-xhdpi-v11/ic_menu_preferences.png differ
diff --git a/term/src/main/res/drawable-xhdpi-v11/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-xhdpi-v11/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..a0e4f60c7
Binary files /dev/null and b/term/src/main/res/drawable-xhdpi-v11/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-xhdpi-v9/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-xhdpi-v9/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..a8ace0982
Binary files /dev/null and b/term/src/main/res/drawable-xhdpi-v9/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-xhdpi/ic_launcher.png b/term/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..f67c5d845
Binary files /dev/null and b/term/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/term/src/main/res/drawable-xhdpi/ic_stat_service_notification_icon.png b/term/src/main/res/drawable-xhdpi/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..8fb6a951b
Binary files /dev/null and b/term/src/main/res/drawable-xhdpi/ic_stat_service_notification_icon.png differ
diff --git a/term/src/main/res/drawable-xxhdpi/ic_launcher.png b/term/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..24b5890d5
Binary files /dev/null and b/term/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/term/src/main/res/drawable-xxxhdpi/ic_launcher.png b/term/src/main/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..884daec33
Binary files /dev/null and b/term/src/main/res/drawable-xxxhdpi/ic_launcher.png differ
diff --git a/term/src/main/res/drawable/btn_close_window.png b/term/src/main/res/drawable/btn_close_window.png
new file mode 100644
index 000000000..53f878beb
Binary files /dev/null and b/term/src/main/res/drawable/btn_close_window.png differ
diff --git a/term/src/main/res/drawable/close_background.xml b/term/src/main/res/drawable/close_background.xml
new file mode 100644
index 000000000..7b266e932
--- /dev/null
+++ b/term/src/main/res/drawable/close_background.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/term/src/main/res/drawable/ic_folder.png b/term/src/main/res/drawable/ic_folder.png
new file mode 100644
index 000000000..13016013f
Binary files /dev/null and b/term/src/main/res/drawable/ic_folder.png differ
diff --git a/term/src/main/res/drawable/ic_folderup.png b/term/src/main/res/drawable/ic_folderup.png
new file mode 100644
index 000000000..d9f53d46f
Binary files /dev/null and b/term/src/main/res/drawable/ic_folderup.png differ
diff --git a/term/src/main/res/drawable/ic_launcher.png b/term/src/main/res/drawable/ic_launcher.png
new file mode 100644
index 000000000..4d8141fb6
Binary files /dev/null and b/term/src/main/res/drawable/ic_launcher.png differ
diff --git a/term/src/main/res/drawable/ic_menu_add.png b/term/src/main/res/drawable/ic_menu_add.png
new file mode 100644
index 000000000..57a4099bb
Binary files /dev/null and b/term/src/main/res/drawable/ic_menu_add.png differ
diff --git a/term/src/main/res/drawable/ic_menu_back.png b/term/src/main/res/drawable/ic_menu_back.png
new file mode 100644
index 000000000..91fa3e74a
Binary files /dev/null and b/term/src/main/res/drawable/ic_menu_back.png differ
diff --git a/term/src/main/res/drawable/ic_menu_close_clear_cancel.png b/term/src/main/res/drawable/ic_menu_close_clear_cancel.png
new file mode 100644
index 000000000..04a76b589
Binary files /dev/null and b/term/src/main/res/drawable/ic_menu_close_clear_cancel.png differ
diff --git a/term/src/main/res/drawable/ic_menu_forward.png b/term/src/main/res/drawable/ic_menu_forward.png
new file mode 100644
index 000000000..632e4f6db
Binary files /dev/null and b/term/src/main/res/drawable/ic_menu_forward.png differ
diff --git a/term/src/main/res/drawable/ic_menu_preferences.png b/term/src/main/res/drawable/ic_menu_preferences.png
new file mode 100644
index 000000000..2f34c7695
Binary files /dev/null and b/term/src/main/res/drawable/ic_menu_preferences.png differ
diff --git a/term/src/main/res/drawable/ic_menu_windows.png b/term/src/main/res/drawable/ic_menu_windows.png
new file mode 100644
index 000000000..c33de2b04
Binary files /dev/null and b/term/src/main/res/drawable/ic_menu_windows.png differ
diff --git a/term/src/main/res/drawable/ic_stat_service_notification_icon.png b/term/src/main/res/drawable/ic_stat_service_notification_icon.png
new file mode 100644
index 000000000..635c6fe83
Binary files /dev/null and b/term/src/main/res/drawable/ic_stat_service_notification_icon.png differ
diff --git a/res/layout/term_activity.xml b/term/src/main/res/layout/term_activity.xml
similarity index 68%
rename from res/layout/term_activity.xml
rename to term/src/main/res/layout/term_activity.xml
index 4daf5245d..b84a51a8c 100644
--- a/res/layout/term_activity.xml
+++ b/term/src/main/res/layout/term_activity.xml
@@ -16,15 +16,9 @@
*/
-->
-
-
-
-
-
+ android:background="@android:color/black"
+ />
diff --git a/term/src/main/res/layout/window_list_item.xml b/term/src/main/res/layout/window_list_item.xml
new file mode 100644
index 000000000..70b040fae
--- /dev/null
+++ b/term/src/main/res/layout/window_list_item.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
diff --git a/term/src/main/res/layout/window_list_new_window.xml b/term/src/main/res/layout/window_list_new_window.xml
new file mode 100644
index 000000000..a017d3267
--- /dev/null
+++ b/term/src/main/res/layout/window_list_new_window.xml
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/res/menu/main.xml b/term/src/main/res/menu/main.xml
similarity index 58%
rename from res/menu/main.xml
rename to term/src/main/res/menu/main.xml
index 60d9d04e8..166245a9e 100644
--- a/res/menu/main.xml
+++ b/term/src/main/res/menu/main.xml
@@ -16,14 +16,30 @@
-->
diff --git a/term/src/main/res/values-cs/arrays.xml b/term/src/main/res/values-cs/arrays.xml
new file mode 100644
index 000000000..b71bc29c7
--- /dev/null
+++ b/term/src/main/res/values-cs/arrays.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+ Zobrazovat stavový řádek
+ Nebrazovat stavový řádek
+
+
+
+
+ Vždy zobrazovat panel akcí
+ Skrývat panel akcí (zobrazit dotykem v horní části obrazovky, nebo tlačítkem menu)
+
+
+
+ Neblikající kurzor \:\)
+ Blikající kurzor
+
+
+
+ Obdelník
+ Podtržítko
+ Svislá čára
+
+
+
+ 4 x 8 pixelů
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Černý text na bílém podkladu
+ Bílý text na černém podkladu
+ Bílý text na modrém podkladu
+ Zelený text na černém podkladu
+ Žlutý text na černém podkladu
+ Červený text na černém podkladu
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Zavřít všechna terminálová okna
+ Zavřít pouze toto terminálové okno
+ Zavřít terminál, ale dál poběží na pozadí
+ Poslat klávesu ESC do terminálu
+ Poslat klávesu TAB do terminálu
+
+
+
+ Pohybové tlačítko
+ Tlačítko \@
+ Levá klávesa ALT
+ Pravá klávesa ALT
+ Tlačítko hlasitosti dolů
+ Tlačítko hlasitosti nahoru
+ Tlačítko kamery
+ Žádné
+
+
+
+ Pohybové tlačítko
+ Tlačítko \@
+ Levá klávesa ALT
+ Pravá klávesa ALT
+ Tlačítko hlasitosti dolů
+ Tlačítko hlasitosti nahoru
+ Tlačítko kamery
+ Žádné
+
+
+
+ Zadávání po znacích
+ Zadávání po slovech
+
+
+
+
+ Kulička
+ \@
+ Levý Alt
+ Pravý Alt
+ Vol-Up
+ Vol-Dn
+ Kamera
+ Žádné
+
+
+
+
+ Kulička
+ \@
+ Levý Alt
+ Pravý Alt
+ Vol-Up
+ Vol-Dn
+ Kamera
+ Žádné
+
+
+
diff --git a/term/src/main/res/values-cs/strings.xml b/term/src/main/res/values-cs/strings.xml
new file mode 100644
index 000000000..f3d19bcce
--- /dev/null
+++ b/term/src/main/res/values-cs/strings.xml
@@ -0,0 +1,127 @@
+
+
+
+ Terminal Emulator
+ Nastavení
+ Nové okno
+ Zavřít okno
+ Okna
+ Predešlé okno
+ Další okno
+ Reset terminálu
+ Napiš email
+ Speciální znaky
+ Přepnout soft. klávesnici
+
+ Toto terminálové okno bylo restartováno.
+
+ Zabránit uspání
+ Povolit uspání
+ Zabránit odpojení wifi
+ Povolit odpojení wifi
+
+ Práce s textem
+ Vybrat text
+ Kopírovat vše
+ Vložit
+ Pošli klávesu control
+ Pošli klávesu fn
+
+ Okno %1$d
+
+ Terminál je spuštěný
+
+ Terminál je ukončen
+
+
+ Obrazovka
+
+ Stavový řádek
+ Zobrazit/skrýt stavový řádek.
+ Stavový řádek
+
+ Panel akcí
+ Vyberte chování panelu akcí (Android 3.0 a novější).
+ Chování panelu akcí
+
+ Styl kurzoru
+ Vyberte styl kurzoru.
+ Styl kurzoru
+
+ Blikání kurzoru
+ Vyberte chování kurzoru.
+ Blikání kurzoru
+
+ Text
+
+ Výchozí formát UTF-8
+ Nastavte formátování UTF-8 jako výchozí.
+
+ Velikost písma
+ Vyberte velikost písma.
+ Velikost písma
+
+ Barvy
+ Vyberte barevné schéma terminálu.
+ Barva textu
+
+ Klávesnice
+
+ Chování tlačítka zpět
+ Vyberte chování tlačítka zpět.
+ Chování tlačítka zpět
+
+ Klávesa control
+ Vyberte tlačítko pro klávesu control.
+ Klávesa control
+
+ Klávesa Fn
+ Vyberte tlačítko pro klávesu Fn.
+ Klávesa Fn
+
+ Vstupní metoda
+ Vyberte vstupní metodu pro soft. klávesnici.
+ Vstupní metoda
+
+ Shell
+ Příkazový řádek
+ Nastavte příkazový řádek.
+ Shell
+
+ Počáteční příkaz
+ Nastavte počáteční příkaz po spuštění terminálu.
+ Počáteční příkaz
+
+ Typ terminálu
+ Vyberte typ terminálu pro interpretaci příkazů.
+ Typ terminálu
+
+ Zavřít okno při ukončení
+ Nastavte, zda má být okno zavřeno po ukončení shellu.
+
+ Funkční klávesy a klávesa control
+
+ CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Není nastaveno žádné tlačitko pro control.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ Není nastaveno žádné tlačitko pro funkční klávesu.
+
+ Zavřít okno?
+
\ No newline at end of file
diff --git a/term/src/main/res/values-de/arrays.xml b/term/src/main/res/values-de/arrays.xml
new file mode 100644
index 000000000..545b8a5b7
--- /dev/null
+++ b/term/src/main/res/values-de/arrays.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+ Statuszeile zeigen
+ Statuszeile verbergen
+
+
+
+ Permanenter Cursor
+ Blinkender Cursor
+
+
+
+ Automatisch
+ Querformat (Landscape)
+ Hochformat (Portrait)
+
+
+
+
+ Aktionsleiste immer anzeigen
+ Aktionsleiste verstecken (zum Anzeigen Bildschirm oben berühren oder Menü-Taste drücken)
+
+
+
+ Rechteck
+ Unterstrichen
+ Vertikaler Balken
+
+
+
+ 4x8 Pixel
+ 6 Punkte
+ 7 Punkte
+ 8 Punkte
+ 9 Punkte
+ 10 Punkte
+ 12 Punkte
+ 14 Punkte
+ 16 Punkte
+ 20 Punkte
+ 24 Punkte
+ 28 Punkte
+ 32 Punkte
+ 36 Punkte
+ 42 Punkte
+ 48 Punkte
+ 64 Punkte
+ 72 Punkte
+ 96 Punkte
+ 144 Punkte
+ 288 Punkte
+
+
+
+ Schwarzer Text auf weiß
+ Weißer Text auf schwarz
+ Weißer Text auf blau
+ Grüner Text auf schwarz
+ Oranger Text auf schwarz
+ Roter Text auf schwarz
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Schließt alle Terminalfenster
+ Schließt das aktuelle Terminalfenster
+ Aktivität schließen, Sessions erhalten lassen
+ Sendet ESC zum Terminal
+ Sendet TAB zum Terminal
+
+
+
+ Trackball
+ \@ Taste
+ Linke Alt-Taste
+ Rechte Alt-Taste
+ Lauter
+ Leiser
+ Kamera-Taste
+ Keine
+
+
+
+ Trackball
+ \@ Taste
+ Linke Alt-Taste
+ Rechte Alt-Taste
+ Lauter
+ Leiser
+ Kamera-Taste
+ Keine
+
+
+
+ Zeichen-basiert
+ Wort-basiert
+
+
+
+
+ Trackball
+ \@ Taste
+ Linke Alt-Taste
+ Rechte Alt-Taste
+ Lauter
+ Leiser
+ Kamera-Taste
+ Keine
+
+
+
+
+ Trackball
+ \@ Taste
+ Linke Alt-Taste
+ Rechte Alt-Taste
+ Lauter
+ Leiser
+ Kamera-Taste
+ Keine
+
+
diff --git a/term/src/main/res/values-de/strings.xml b/term/src/main/res/values-de/strings.xml
new file mode 100644
index 000000000..0ee30c555
--- /dev/null
+++ b/term/src/main/res/values-de/strings.xml
@@ -0,0 +1,164 @@
+
+
+
+ Terminal Emulator
+ Einstellungen
+ Neues Fenster
+ Fenster schließen
+ Fenster
+ Vorh. Fenster
+ Nächst. Fenster
+ Zurücksetzen
+ Spezialtasten
+ Tastatur an/aus
+
+ Der Status dieses Terminalfensters wurde zurückgesetzt.
+
+ Bildschirm immer anlassen
+ Bildschirm ist immer an
+ WLAN immer anlassen
+ WLAN ist immer an
+
+ Text bearbeiten
+ Text auswählen
+ Alles kopieren
+ Einfügen
+
+ Terminalsitzung wird ausgeführt
+
+ Fenster %1$d
+
+ Terminalsitzung beendet
+
+ Bildschirm
+
+ Statuszeile
+ Die Statuszeile anzeigen/verbergen.
+ Statuszeile
+
+ Aktionsleiste
+ Wählen sie das Verhalten der Aktionsleiste (ab Android 3.0).
+ Verhalten der Aktionsleiste
+
+ Aussehen des Cursor
+ Das Aussehen des Cursors auswählen.
+ Aussehen des Cursor
+
+ Cursor-Blinken
+ Die Art auswählen, wie der Cursor blinken soll.
+ Cursor-Blinken
+
+ Text
+
+ UTF-8 als Standard definieren
+ Ist der UTF-8 Modus standartmäßig aktiv?
+
+ Schriftgröße
+ Die Schriftgröße in Punkten auswählen.
+ Schriftgröße
+
+ Farben
+ Die Text- und Hintergrundfarbe auswählen.
+ Textfarbe
+
+ Tastatur
+
+ Zurück-Knopf
+ Festlegen der Aktion bei Drücken des Zurück-Knopfes.
+ Zurück-Knopf
+
+ Steuerungstaste (Strg)
+ Die Steuerungstaste auswählen.
+ Steuerungstaste (Strg)
+
+ Funktionstaste (Fn)
+ Die Funktionstaste auswählen.
+ Funktionstaste (Fn)
+
+ Eingabemethode
+ Die Eingabemethode für die Bildschirmtastatur auswählen.
+ Eingabemethode
+
+ Shell
+ Shell
+ Die zu verwendene Shell festlegen.
+ Shell
+
+ Startbefehl
+ Befehl eingeben, der beim Start an die Shell gesendet wird.
+ Startbefehl
+
+ Terminal-Typ
+ Welchen Terminal-Typ soll die Shell ausführen?
+ Terminal-Typ
+
+ Fenster mit Shell beenden
+ Soll das Fenster geschlossen werden, wenn die Shell beendet wird?
+
+ Steuerungs- und Funktionstasten
+
+ CTRLKEY + Leertaste: Strg-@ (NUL)\nCTRLKEY + A..Z: Strg-A..Z\nCTRLKEY + 5: Strg-]\nCTRLKEY + 6: Strg-^\nCTRLKEY + 7: Strg-_\nCTRLKEY + 9: F11\nCTRLKEY + 0: F12
+ Keine Steuerungstaste festgelegt.
+
+ FNKEY + 1..9: F1-F9\nFNKEY + 0: F10\nFNKEY + W: ↑\nFNKEY + A: ←\nFNKEY + S: ↓\nFNKEY + D: →\nFNKEY + P: Bild↑\nFNKEY + N: Bild↓\nFNKEY + T: Tab\nFNKEY + L : | (pipe)\nFNKEY + U: _ (Unterstrich)\nFNKEY + E: Strg-[ (ESC)\nFNKEY + X: Entfernen\nFNKEY + I: Einfügen\nFNKEY + H: Pos1\nFNKEY + F: Ende\nFNKEY + .: Strg-\\\n
+ Keine Funktionstaste fergelegt.
+
+ Dieses Fenster schließen?
+
+ Beliebige Scripte im Terminal Emulator ausführen
+ Erlaubt Anwendungen, neue Fenster im Terminal Emulator for Android zu öffnen und in diesen Befehle auszuführen. Dies schließt alle Berechtigungen von Terminal Emulator for Android ein, inklusive Internetzugang und Schreib-/Leserechte auf der SD-Karte.
+ Terminal hier
+ "Terminal Verknüpfung"
+ Argumente\:
+ Alt Taste sendet ESC
+ Alt Taste sendet ESC.
+ Alt Taste sendet ESC nicht.
+ B
+ G
+ R
+ Bildschirmausrichtung
+ Konnte keine App zum Verschicken der E-Mail finden
+ E-Mail Verschicken mit:
+ Auszug von Terminal Emulator for Android
+ Theme ändern
+ Externer Speicher nicht verfügbar
+ Oder hier einen Pfad eingeben.
+ DATEI AUSWÄHLEN
+ Hife
+ Befehle zum Terminal Emulator hinzufügen
+ Befehle im Terminal Emulator Überschreiben
+ Erlaubt es Applikationen dem Terminal Emulator für Android zusätzliche Befehle zur Verfügung zu stellen (fügt Verzeichnisse an die PATH Valriable an).
+ Erlaubt es Applikationen im Terminal Emulator für Android existierende Befehle mit eigenen Versionen zu überschreiben (Fügt Verzeichnisse am Anfang der PATH Variable ein).
+ Tastenkombinationen deaktiviert.
+ Überprüfe PATH Einträge
+ Nutze Tastenkombinationen
+ Bildschirmausrichtung
+ Sende Maus-Ereignisse
+ HOME Verzeichniss
+ Erlaube PATH Erweiterungen
+ Sollen Verzeichnisse, auf welche nicht zugegriffen werden kann, vom PATH entfernt werden?
+ Wähle die Bilschirmausrichtung
+ Sollen Tipp- und Scroll-Ereignisse als Escape-Sequenzen ans Terminal gesendet werden?
+ Pfad zu einem beschreibbaren Verzeichniss welches als HOME genutzt werden soll.
+ Soll es anderen Apps erlaubt sein zusätliche Befehle zur Verfügung zu stellen? (An PATH anfügen)
+ Soll es anderen Apps erlaubt sein, existierende Befehle zu überschreiben (Vor PATH einfügen).
+ Sende FN-Taste
+ Teilen per E-Mail
+ Sende Strg-Taste
+
diff --git a/term/src/main/res/values-es/arrays.xml b/term/src/main/res/values-es/arrays.xml
new file mode 100644
index 000000000..eff3ae510
--- /dev/null
+++ b/term/src/main/res/values-es/arrays.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+ Mostrar
+ Ocultar
+
+
+
+
+ Mostrar
+ Ocultar (Pulsa MENÚ para mostrarla)
+
+
+
+ Automática
+ Horizontal
+ Vertical
+
+
+
+ Cursor no-parpadeante
+ Cursor parpadeante
+
+
+
+ Rectángulo
+ Subrayado
+ Barra vertical
+
+
+
+ 4 x 8 píxeles
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Negro en fondo blanco
+ Blanco en fondo negro
+ Blanco en fondo azul
+ Verde en fondo negro
+ Ámbar en fondo negro
+ Rojo en fondo negro
+ Azul en fondo negro
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Cierra todas las ventanas
+ Cierra sólo la ventana actual
+ Sale del terminal, dejándolo en segundo plano
+ Envía ESC al terminal
+ Envía TAB al terminal
+
+
+
+ Track ball
+ Tecla \@
+ Tecla Alt izquierda
+ Tecla Alt derecha
+ Tecla de subir volumen
+ Tecla de bajar volumen
+ Tecla de la cámara
+ Ninguna
+
+
+
+ Track ball
+ Tecla \@
+ Tecla Alt izquierda
+ Tecla Alt derecha
+ Tecla de subir volumen
+ Tecla de bajar volumen
+ Tecla de la cámara
+ Ninguna
+
+
+
+ Basada en carácteres
+ Basada en palabras
+
+
+
+
+ T.Ball
+ \@
+ Alt.Izq.
+ Alt.Dcha.
+ Vol.Subir
+ Vol.Bajar
+ Cámara
+ Ninguna
+
+
+
+
+ T.Ball
+ \@
+ Alt.Izq.
+ Alt.Dcha.
+ Vol.Subir
+ Vol.Bajar
+ Cámara
+ Ninguna
+
+
diff --git a/term/src/main/res/values-es/strings.xml b/term/src/main/res/values-es/strings.xml
new file mode 100644
index 000000000..101402711
--- /dev/null
+++ b/term/src/main/res/values-es/strings.xml
@@ -0,0 +1,197 @@
+
+
+
+ Terminal
+ Preferencias
+ Nueva ventana
+ Cerrar ventana
+ Ventanas
+ Ventana anterior
+ Ventana posterior
+ Reiniciar consola
+ Enviar email
+ Teclas especiales
+ Ver/ocultar teclado
+
+ Ventana de terminal reiniciada.
+
+ Activar WakeLock
+ Desactivar WakeLock
+ Activar WifiLock
+ Desactivar WifiLock
+
+ Editar texto
+ Seleccionar texto
+ Copiar todo
+ Pegar
+ Tecla asignada a Ctrl
+ Tecla asignada a FN
+
+ Ventana %1$d
+
+ Sesión de terminal en ejecución
+
+ Sesión de Terminal finalizada
+
+
+ Pantalla
+
+ Barra de estado
+ Ver/ocultar barra de estado.
+ Barra de estado
+
+ Barra de tareas
+ Elije el comportamiente de la barra de tareas (Android 3.0+).
+ Barra de tareas
+
+ Orientación de pantalla
+ Elije el comportamiento de la orientación de pantalla.
+ Comportamiento de orientación de pantalla
+
+ Diseño del cursor
+ Selecciona el diseño del cursor.
+ Diseño del cursor
+
+ Parpadeo del cursor
+ Elije el parpadeo del cursor.
+ Parpadeo del cursor
+
+ Texto
+
+ UTF-8 por defecto
+ Activa por defecto la codificación UTF-8
+
+ Tamaño de fuente
+ Elije el tamaño de la fuente.
+ Tamaño de fuente
+
+ Colores
+ Elije el color del texto.
+ Color del texto
+
+ Teclado
+
+ Acción del botón ATRÁS
+ Selecciona la acción al pulsar el botón ATRÁS.
+ Acción del botón ATRÁS
+
+ Tecla Ctrl
+ Elije la tecla Ctrl.
+ Tecla Ctrl
+
+ Tecla FN
+ Elije la tecla FN.
+ Tecla FN
+
+ Método de introducción
+ Elige el método de introducción.
+ Método de introducción
+
+ Shell
+ Lineas de comandos
+ Especifique el shell para la línea de comandos.
+ Shell
+
+ Comando inicial
+ Comando que se envía a la consola al iniciarla.
+ Comando inicial
+
+ Tipo de terminal
+ Que tipo de terminal se indica al shell.
+ Tipo de terminal
+
+ Cerrar ventana al salir
+ Elegir si la ventana se cerrará al salir de ella.
+
+ Verificar entradas de PATH
+ Los directorios inaccesibles serán eliminados de PATH.
+
+ Permitir adiciones
+ Se permite a otras aplicaciones añadir entradas al PATH.
+
+ Permitir anteponer adiciones
+ Otras aplicaciones pueden sobreescribir entradas ya existentes del PATH
+
+ Carpeta HOME
+ Ruta a una carpeta con permisos de escritura para usar como HOME.
+
+ Teclas FN y Ctrl
+
+ CTRLKEY Espacio : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ No hay tecla definida para Ctrl.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : AvPágina\nFNKEY N : RetPágina\nFNKEY T : Tab\nFNKEY L : | (tubería)\nFNKEY U : _ (subrayado)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Suprimir\nFNKEY I : Insertar\nFNKEY H : Inicio\nFNKEY F : Fin\nFNKEY . : Control-\\\n
+ No hay tecla definida para FN.
+
+ ¿Cerrar esta ventana?
+
+ Permitir que la aplicación ejecute comandos arbitrarios
+ Permitir a las aplicaciones abrir nuevas ventanas de terminal con todos los permisos concedidos, incluso los de acceso a Internet y a la tarjeta SD.
+ Añadir comandos al Terminal
+ Otras aplicaciones pueden añadir nuevos comandos al PATH.
+ Sobreescribir comandos del Terminal
+ Otras aplicaciones pueden sobreescribir comandos ya existentes del PATH.
+
+ Enviar correo electrónico sobre Terminal
+ Enviar correo usando:
+ No hay aplicaciones de email disponibles.
+
+ Tecla Alt como ESC
+ La tecla Alt funciona como ESC.
+ La tecla Alt no funciona como ESC.
+
+ Enviar eventos de ratón
+ Cuando los eventos de pulsación y desplazamiento deben mandarse como secuencias de escape al terminal.
+
+ Usar Atajos de Teclado
+ Ctrl-Tab: cambiar ventana, Ctrl-Shift-N: nueva ventana, Ctrl-Shift-V: pegar.
+ Atajos de teclado desactivados.
+
+ Ayuda
+ Terminal aquí
+
+
+
+ "Terminal (acceso directo)"
+
+ EXPLORADOR DE ARCHIVOS
+ Almacenamiento externo no disponible
+ O intruduce ruta aquí.
+ Cambiar tema
+
+ comando
+ --ejemplo=\""a\"
+ Encontrar comando
+ ELIJE DESTINO DEL ACCESO DIRECTO
+ La ventana de comandos necesita la ruta completa, sin argumentos. Para otros comandos usa la ventana de Argumentos (ej: cd /sdcard).
+ Argumentos\:
+ Etiqueta acceso directo\:
+ Texto del icono
+ HACER ICONO DE TEXTO
+ Acceso directo a terminal
+
+ α
+ R
+ G
+ B
+ BLOQUEAR
+ Introduce texto del icono
+
+
+
diff --git a/term/src/main/res/values-eu/arrays.xml b/term/src/main/res/values-eu/arrays.xml
new file mode 100644
index 000000000..891abeda0
--- /dev/null
+++ b/term/src/main/res/values-eu/arrays.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+ Erakutsi egoera-barra
+ Ezkutatu egoera-barra
+
+
+
+
+ Beti erakutsi ekintza-barra
+ Ezkutatu ekintza-barra (erakusteko ukitu pantailaren goialdea edo Menu tekla)
+
+
+
+ Kurtsore ez-keinukaria
+ Kurtsore keinukaria
+
+
+
+ Laukizuzena
+ Azpimarra
+ Barra bertikala
+
+
+
+ 4 x 8 pixel
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Testu beltza zuriaren gainean
+ Testu zuria beltzaren gainean
+ Testu zuria urdinaren gainean
+ Testu berdea beltzaren gainean
+ Testu anbarra beltzaren gainean
+ Testu gorria beltzaren gainean
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Terminal leiho guztiak ixten ditu
+ Soilik terminal leiho hau ixten du
+ Jarduera ixten du, saioak martxan utziz
+ ESC bidaltzen du terminalera
+ TAB bidaltzen du terminalera
+
+
+
+ Jog bola
+ \@ tekla
+ Ezker Alt tekla
+ Eskuin Alt tekla
+ Bol Igo tekla
+ Bol Jaitsi tekla
+ Kamera tekla
+ Bat ere ez
+
+
+
+ Jog bola
+ \@ tekla
+ Ezker Alt tekla
+ Eskuin Alt tekla
+ Bol Igo tekla
+ Bol Jaitsi tekla
+ Kamera tekla
+ Bat ere ez
+
+
+
+ Karaktereetan oinarritua
+ Hitzetan oinarritua
+
+
+
+
+ Bola
+ \@
+ Ezker-Alt
+ Eskuin-Alt
+ Bol-Igo
+ Bol-Jaitsi
+ Kamera
+ Bat ere ez
+
+
+
+
+ Bola
+ \@
+ Ezker-Alt
+ Eskuin-Alt
+ Bol-Igo
+ Bol-Jaitsi
+ Kamera
+ Bat ere ez
+
+
+
+
diff --git a/term/src/main/res/values-eu/strings.xml b/term/src/main/res/values-eu/strings.xml
new file mode 100644
index 000000000..b9f058681
--- /dev/null
+++ b/term/src/main/res/values-eu/strings.xml
@@ -0,0 +1,131 @@
+
+
+
+ Terminal Emulatzailea
+ Hobespenak
+ Leiho berria
+ Leihoa itxi
+ Leihoak
+ Aurreko leihoa
+ Hurrengo leihoa
+ Berrezarri terminala
+ Bidali eposta ...(r)i
+ Tekla bereziak
+ Txandakatu soft teklatua
+
+ -Terminal leiho honen egoera berrezarri da.
+
+ Gaitu WakeLock
+ Desgaitu WakeLock
+ Gaitu WifiLock
+ Desgaitu WifiLock
+
+ Editatu testua
+ Hautatu testua
+ Kopiatu guztia
+ Itsatsi
+ Bidali kontrol tekla
+ Bidali fn tekla
+
+ %1$d leihoa
+
+ Terminal saioa martxan dago
+
+ Terminal saioa amaituta
+
+
+ Pantaila
+
+ Egoera-barra
+ Erakutsi/ezkutatu egoera-barra.
+ Egoera-barra
+
+ Ekintza-barra
+ Aukeratu ekintza-barraren portaera (Android 3.0tik aurrera).
+ Ekintza-barraren portaera
+
+ Kurtsore-estiloa
+ Hautatu kurtsore-estiloa.
+ Kurtsore-estiloa
+
+ Kurtsore-keinua
+ Hautatu kurtsore-keinua.
+ Kurtsore-keinua
+
+ Testua
+
+ Lehenetsi UTF-8
+ UTF-8 modu lehenetsian gaitzen den ala ez.
+
+ Letra-tamaina
+ Hautatu karaktereen tamaina puntutan.
+ Letra-tamaina
+
+ Koloreak
+ Hautatu testuaren kolorea.
+ Testuaren kolorea
+
+ Teklatua
+
+ Atzera botoiaren portaera
+ Aukeratu atzera botoia sakatzeak zer egiten duen.
+ Atzera botoiaren portaera
+
+ Kontrol tekla
+ Hautatu kontrol tekla.
+ Kontrol tekla
+
+ Fn tekla
+ Hautatu Fn tekla.
+ Fn tekla
+
+ Sarrera-metodoa
+ Hautatu sarrera-metodoa soft teklatuarentzat.
+ Sarrera-metodoa
+
+ Shell
+ Komando lerroa
+ Zehaztu shell komando lerroa.
+ Shell
+
+ Hasierako komandoa
+ Bidali shell-era abiarazten denean.
+ Hasierako Komandoa
+
+ Terminal mota
+ Terminal motaren berri eman shell-ari.
+ Terminal mota
+
+ Itxi leihoa irtetean
+ Bere shell-a irtetean leiho bat itxi behar litzatekeen ala ez.
+
+ Kontrol and Funtzio Teklak
+
+ CTRLKEY Zuriunea : Kontrol-@ (NUL)\nCTRLKEY A..Z : Kontrol-A..Z\nCTRLKEY 5 : Kontrol-]\nCTRLKEY 6 : Kontrol-^\nCTRLKEY 7 : Kontrol-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Ez da kontrol teklarik ezarri.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Gora\nFNKEY A : Ezkerra\nFNKEY S : Behera\nFNKEY D : Eskuina\nFNKEY P : Hurrengo-orria\nFNKEY N : Aurreko-orria\nFNKEY T : Tabulatzailea\nFNKEY L : | (kanalizazioa)\nFNKEY U : _ (azpimarra)\nFNKEY E : Kontrol-[ (ESC)\nFNKEY X : Ezabatu\nFNKEY I : Txertatu\nFNKEY H : Etxea\nFNKEY F : Bukaera\nFNKEY . : Kontrol-\\\n
+ Ez da funtzio teklarik ezarri.
+
+ Leiho hau itxi?
+
+ Exekutatu hautazko komandoak Terminal Emulatzailean
+ Aplikazioari baimena ematen dio Android Terminal Emulatzailean leiho berriak irekitzeko eta leiho hauetan komandoak exekutatzeko Android Terminal Emulatzailearen baimen guztiekin, Interneterako eta zure SD txartelerako sarbidea barne.
+
+
diff --git a/term/src/main/res/values-fr/arrays.xml b/term/src/main/res/values-fr/arrays.xml
new file mode 100644
index 000000000..6a6b46fdc
--- /dev/null
+++ b/term/src/main/res/values-fr/arrays.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+ Afficher la barre de statut
+ Masquer la barre de statut
+
+
+
+
+ Toujours afficher la barre d\'actions
+ Cacher la barre d\'actions (Touche Haut de l\'écran ou la touche Menu de l\'appareil pour afficher)
+
+
+
+ Automatique
+ Paysage
+ Portrait
+
+
+
+ Curseur non clignotant
+ Curseur clignotant
+
+
+
+ Rectangle
+ Souligné
+ Barre verticale
+
+
+
+ Texte noir sur blanc
+ Texte blanc sur noir
+ Texte blanc sur bleu
+ Texte vert sur noir
+ Texte orange sur noir
+ Texte rouge sur noir
+ Solarized Light
+ Solarized Dark
+ Console Linux
+
+
+
+ Fermer toutes les fenêtres
+ Fermer la fenêtre en cours
+ Quitter en laissant les sessions activées
+ Simuler la touche ESC
+ Simuler la touche TAB
+
+
+
+ Trackball
+ Touche \@
+ Touche Alt gauche
+ Touche Alt droite
+ Touche Vol Haut
+ Touche Vol Bas
+ Touche Camera
+
+
+
+ Trackball
+ Touche \@
+ Touche Alt gauche
+ Touche Alt droite
+ Touche Vol Haut
+ Touche Vol Bas
+ Touche Camera
+
+
+
+ Par caractère
+ Par mot
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
diff --git a/term/src/main/res/values-fr/strings.xml b/term/src/main/res/values-fr/strings.xml
new file mode 100644
index 000000000..cf664cc04
--- /dev/null
+++ b/term/src/main/res/values-fr/strings.xml
@@ -0,0 +1,163 @@
+
+
+
+
+ Terminal Emulator
+ Paramètres
+ Nouvelle fenêtre
+ Fermer fenêtre
+ Fenêtres
+ Fenêtre Préc.
+ Fenêtre Suiv.
+ Terminal par défaut
+ Envoyer un e-mail
+ Touches spéciales
+ Afficher/Masquer clavier
+
+ État de cette fenêtre par défaut.
+
+ Bloquer la mise en veille
+ Débloquer la mise en veille
+ Bloquer le WiFi
+ Débloquer le WiFi
+
+ Modifier le texte
+ Sélectionner le texte
+ Tout copier
+ Coller
+ Touche Control
+ Touche Fonction
+
+ Fenêtre %1$d
+
+ Session terminal en cours d\'exécution
+
+ Session terminal terminée
+
+ Fermer cette fenêtre ?
+
+
+ Écran
+
+ Barre de statut
+ Afficher/Masquer la barre de statut
+ Barre de statut
+
+ Barre d\'actions
+ Choisir le comportement de la barre d\'actions (à partir d\'Android 3.0)
+ Barre d\'actions
+
+ Style du curseur
+ Choisir le style du curseur
+ Style du curseur
+
+ Clignotement curseur
+ Choisir si le curseur doit clignoter
+ Clignotement curseur
+
+ Texte
+
+ UTF-8 par défaut
+ Activer le mode UTF-8 par défaut
+
+ Taille police
+ Choisir la taille de la police de caractères en points
+ Taille de la police
+
+ Couleurs
+ Choisir la couleur du texte
+ Couleur du texte
+
+ Clavier
+
+ Bouton retour
+ Choisir l\'action du bouton retour quand il est cliqué
+ Bouton retour
+
+ Touche CTRL
+ Choisir quelle touche utiliser pour Control (CTRL)
+ Touche CTRL
+
+ Touche Fn
+ Choisir quelle touche utiliser pour Fonction (Fn)
+ Touche Fn
+
+ Méthode d\'entrée
+ Choisir la méthode d\'entrée du clavier virtuel
+ Méthode d\'entrée
+
+ Touche Alt
+ Alt simule la touche Echap
+ Alt ne simule pas la touche Echap
+
+ Shell
+ Ligne de commande
+ Régler la ligne de commande du shell
+ Shell
+
+ Commande initiale
+ Choisir une commande au démarrage du shell
+ Commande initiale
+
+ Type de terminal
+ Type de terminal à reporter au shell
+ Type de terminal
+
+ Quitter avec exit
+ Quitter la fenêtre en cours en exécutant la commande exit
+
+ Vérifier le PATH
+ Les répertoires inaccessibles seront retirés de PATH
+
+ Extensions de PATH
+ D\'autres applications sont autorisées à fournir des commandes supplémentaires (à ajouter au PATH)
+
+ Préfixe de PATH
+ D\'autres applications sont autorisées à remplacer les commandes existantes (à ajouter au début de PATH)
+
+ Dossier HOME
+ Chemin à un dossier accessible en écriture pour être utilisé comme HOME.
+
+ Touches Control et Fonction
+
+ CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Touche Control non paramètrée.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Haut\nFNKEY A : Gauche\nFNKEY S : Bas\nFNKEY D : Droite\nFNKEY P : PageHaut\nFNKEY N : PageBas\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Suppr.\nFNKEY I : Inser.\nFNKEY H : Début\nFNKEY F : Fin\nFNKEY . : Control-\\\n
+ Touche fonction non paramètrée.
+
+ Transcription depuis Terminal Emulator
+ La transcription par email utilise :
+ Impossible de choisir une activité de messagerie pour envoyer la transcription
+
+ Lancer des commandes tierces
+ Permettre à une application d\'ouvrir une nouvelle fenêtre dans Terminal Emulator en exécutant des commandes avec les permissions de l\'application comme l\'accès à Internet et à votre carte SD
+ Ajouter des commandes
+ Permettre à une application de fournir des commandes supplémentaires (ajouter le répertoire au PATH) pour Terminal Emulator
+ Remplacer des commandes
+ Permettre à une application de remplacer des commandes existantes avec ses versions (répertoire à ajouter en préfixe au PATH) dans Terminal Emulator
+
+ Envoyer des événements de souris
+ Aussi bien les événements d\'appui et de scroll doivent être envoyés sous forme de séquences d\'échappement au terminal.
+
+ Utiliser les raccourcis clavier
+ Ctrl-Tab: cycle window, Ctrl-Shift-N: nouvelle fenêtre, Ctrl-Shift-V: coller.
+ Raccourcis clavier désactivés.
+
+ Aide
+
+
diff --git a/term/src/main/res/values-hu/arrays.xml b/term/src/main/res/values-hu/arrays.xml
new file mode 100644
index 000000000..7e4234f14
--- /dev/null
+++ b/term/src/main/res/values-hu/arrays.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+ Megjelenítés
+ Elrejtés
+
+
+
+
+ Mindig jelenjen meg
+ Elrejtés (a megjelenítéshez érintsd meg a képernyő tetejét vagy a menü gombot)
+
+
+
+ Automatikus
+ Fekvő
+ Álló
+
+
+
+ Statikus kurzor
+ Villogó kurzor
+
+
+
+ Téglalap
+ Aláhúzás
+ Függőleges
+
+
+
+ 4 x 8 képpont
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Fekete szöveg, fehér háttér
+ Fehér szöveg, fekete háttér
+ Fehér szöveg, kék háttér
+ Zöld szöveg, fekete háttér
+ Sárga szöveg, fekete háttér
+ Piros szöveg, fekete háttér
+ Holo kék szöveg, fekete háttér
+ Solarized világos
+ Solarized sötét
+ Linux konzol
+
+
+
+ Bezárja az összes terminál ablakot
+ Bezárja az aktív terminál ablakot
+ Kilép a programból, de a munkamenetből nem
+ ESC-et küld a terminálra
+ TAB-ot küld a terminálra
+
+
+
+ Görgő
+ \@ billentyű
+ Bal Alt billentyű
+ Jobb Alt billentyű
+ Hangerő fel gomb
+ Hangerő le gomb
+ Kamera gomb
+ Nincs
+
+
+
+ Görgő
+ \@ billentyű
+ Bal Alt billentyű
+ Jobb Alt billentyű
+ Hangerő fel gomb
+ Hangerő le gomb
+ Kamera gomb
+ Nincs
+
+
+
+ Karakterenkénti bevitel
+ Szavankénti bevitel
+
+
+
+
+ Görgő
+ \@
+ Bal-Alt
+ Jobb-Alt
+ Hangerő-fel
+ Hangerő-le
+ Kamera
+ Nincs
+
+
+
+
+ Görgő
+ \@
+ Bal-Alt
+ Jobb-Alt
+ Hangerő-fel
+ Hangerő-le
+ Kamera
+ Nincs
+
+
+
diff --git a/term/src/main/res/values-hu/strings.xml b/term/src/main/res/values-hu/strings.xml
new file mode 100644
index 000000000..a21a384ee
--- /dev/null
+++ b/term/src/main/res/values-hu/strings.xml
@@ -0,0 +1,194 @@
+
+
+
+ Terminál Emulátor
+ Beállítások
+ Új ablak
+ Ablak bezárása
+ Ablakok
+ Előző ablak
+ Következő ablak
+ Alaphelyzet
+ Küldés emailben
+ Speciális billentyűk
+ Billentyűzet ki/be
+
+ A terminál ablak alaphelyzetbe állítva.
+
+ Alvás megakadályozása
+ Alvás engedélyezése
+ Wi-Fi-alvás megakadályozása
+ Wi-Fi-alvás engedélyezése
+
+ Szöveg szerkesztése
+ Szöveg kijelölése
+ Az összes másolása
+ Beillesztés
+ Control billentyű küldése
+ Fn billentyű küldése
+
+ Ablak %1$d
+
+ Aktív munkamenet
+
+ A munkamenet befejeződött
+
+
+ Képernyő
+
+ Értesítési sáv
+ Értesítési sáv megjelenítése/elrejtése.
+ Értesítési sáv
+
+ Akciósáv
+ Válassza ki az akciósáv viselkedési módját (csak Android 3.0-tól).
+ Akciósáv viselkedése
+
+ Kijelző tájolása
+ Válassza ki a képernyő tájolásának módját.
+ Kijelző tájolása
+
+ Kurzor stílusa
+ Válassza ki a kurzor stílusát.
+ Kurzor stílusa
+
+ Kurzor villogás
+ Állítsa be a kurzor villogását.
+ Kurzor villogás
+
+ Szöveg
+
+ UTF-8 alapértelmezett
+ UTF-8 mód engedélyezése alapértelmezésként.
+
+ Betűméret
+ Karakterek magasságának megadása pontokban.
+ Betűméret
+
+ Színek
+ Válassza ki a szöveg és a háttér színét.
+ Színek
+
+ Billentyűzet
+
+ Vissza gomb viselkedése
+ Válassza ki, mi történjen a Vissza gomb megnyomásakor.
+ Vissza gomb viselkedése
+
+ Control billentyű
+ Válassza ki a Control billentyűleütést kiváltó gombot.
+ Control billentyű
+
+ Funkció billentyű
+ Válassza ki a funkció billentyűket kiváltó gombot.
+ Funkció billentyű
+
+ Beviteli mód
+ Beviteli mód kiválasztása a szoftveres billentyűzethez.
+ Beviteli mód
+
+ Konzol
+ Parancssor
+ Adja meg a konzolt elindító parancsot.
+ Konzol
+
+ Indító parancs
+ Induláskor lefuttatja a konzol.
+ Indító parancs
+
+ Terminál típusa
+ Milyen terminál típust jelentsen a konzolnak.
+ Terminál típusa
+
+ Ablak bezárása kilépéskor
+ Terminál ablak bezárása, ha a konzol is bezáródik.
+
+ PATH bejegyzések ellenőrzése
+ A nem hozzáférhető útvonalak eltávolítása a PATH változóból.
+
+ PATH kiterjesztésének engedélyezése
+ Az alkalmazások felvehetnek új parancsokat (hozzáfűzhetnek a PATH változóhoz).
+
+ PATH felülírásának engedélyezése
+ Az alkalmazások felülírhatják a meglévő parancsokat (hozzáfűzhetnek a PATH változó elejéhez).
+
+ HOME könyvtár
+ Egy írható könyvtár útvonala, amely HOME-ként szolgál.
+
+ Control és funkció billentyűk
+
+ CTRLKEY Szóköz : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Nincs beállítva Control billentyű.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Fel\nFNKEY A : Balra\nFNKEY S : Le\nFNKEY D : Jobbra\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (aláhúzás)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ Nincs beállítva funkció billentyű.
+
+ Bezárja ezt az ablakot?
+
+ Tetszőleges parancsok futtatása a Terminál Emulátorban
+ Lehetővé teszi az alkalmazások számára új ablakok megnyitását az Android Terminál Emulátorban és a megnyitott ablakokban tetszőleges parancsokat futtathatnak az Android Terminál Emulátor összes jogosultságával, beleértve a teljes hozzáférést az Internethez és az SD-kártyához.
+ Parancsok hozzáadása a Terminál Emulátorhoz
+ Az alkalmazások felvehetnek új parancsokat (hozzáfűzhetnek a PATH változóhoz) az Android Terminál Emulátorban.
+ Parancsok felülírása a Terminál Emulátorban
+ Az alkalmazások felülírhatják a meglévő parancsokat (hozzáfűzhetnek a PATH változó elejéhez) az Android Terminál Emulátorban.
+
+ Android Terminal Emulátor terminál másolat
+ Terminál küldése:
+ Nem sikerült kiválasztani az email alkalmazást.
+
+ Az Alt billentyű ESC-et küld
+ Az Alt billentyű ESC-et küld.
+ Az Alt billentyű nem ESC-et küld.
+
+ Egér események küldése
+ Érintés és görgetés események küldése a terminálra escape szekvenciaként.
+
+ Billentyű-parancsok használata
+ Ctrl-Tab: váltás az ablakok között, Ctrl-Shift-N: új ablak, Ctrl-Shift-V: beillesztés.
+ Billentyű-parancsok letiltva.
+
+ Súgó
+
+
+ "Terminál parancsikon"
+
+ FÁJL MEGNYITÁSA
+ Külső tároló nem elérhető
+ vagy adja meg az útvonalat
+ Téma váltása
+
+ parancs
+ --példa=\""a\"
+ Parancs keresése
+ PARANCS KERESÉSE
+ A parancs mezőbe teljes útvonalat kell megadni, paraméterek nélkül. A parancs paramétereit a Paraméterek mezőben lehet megadni (pl.: cd /sdcard).
+ Paraméterek\:
+ Ikon címkéje\:
+ Szöveges ikon
+ SZÖVEGES IKON LÉTREHOZÁSA
+ Terminál parancsikon
+
+ α
+ R
+ G
+ B
+ RÖGZÍT
+ Adjon meg szöveget az ikonhoz
+
+
diff --git a/term/src/main/res/values-it/arrays.xml b/term/src/main/res/values-it/arrays.xml
new file mode 100644
index 000000000..45b3e7d9f
--- /dev/null
+++ b/term/src/main/res/values-it/arrays.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+ Mostra barra di stato
+ Nascondi barra di stato
+
+
+
+
+ Mostra sempre barra di azione
+ Nascondi barra di azione (tocca cima dello schermo o tasto Menu per mostrarla)
+
+
+
+ Automatico
+ Orizzontale
+ Verticale
+
+
+
+ Cursore fisso
+ Cursore lampeggiante
+
+
+
+ Rettangolo
+ Sottolineatura
+ Barra verticale
+
+
+
+ 4 x 8 pixels
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Testo nero su bianco
+ Testo bianco su nero
+ Testo bianco su blu
+ Testo verde su nero
+ Testo ambra su nero
+ Testo rosso su nero
+ Testo blu brillante su nero
+ Solarizzato Chiaro
+ Solarizzato Scuro
+ Console Linux
+
+
+
+ Chiude tutte le finestre terminale
+ Chiude solo questa finestra terminale
+ Chiude l\'attività, lasciando attive le sessioni
+ Invia ESC al terminale
+ Invia TAB al terminale
+
+
+
+ Trackball
+ \@
+ Alt sinistro
+ Alt destro
+ Volume +
+ Volume -
+ Fotocamera
+ Nessuno
+
+
+
+ Trackball
+ \@
+ Alt sinistro
+ Alt destro
+ Volume +
+ Volume -
+ Fotocamera
+ Nessuno
+
+
+
+ Basato su carattere
+ Basato su parola
+
+
+
+
+ T.ball
+ \@
+ AltSX
+ AltDX
+ Vol+
+ Vol-
+ Camera
+ Ness.
+
+
+
+
+ T.ball
+ \@
+ AltSX
+ AltDX
+ Vol+
+ Vol-
+ Camera
+ Ness.
+
+
diff --git a/term/src/main/res/values-it/strings.xml b/term/src/main/res/values-it/strings.xml
new file mode 100644
index 000000000..cd7956d66
--- /dev/null
+++ b/term/src/main/res/values-it/strings.xml
@@ -0,0 +1,166 @@
+
+
+
+ Emulatore terminale
+ Impostazioni
+ Nuova finestra
+ Chiudi finestra
+ Finestre
+ Fin. successiva
+ Fin. precedente
+ Reset terminale
+ Invia email
+ Tasti speciali
+ Mostra/nascondi tastiera
+
+ Lo stato di questa finestra terminale è stato resettato.
+
+ Attiva WakeLock
+ Disattiva WakeLock
+ Attiva WifiLock
+ Disattiva WifiLock
+
+ Modifica testo
+ Seleziona testo
+ Copia tutto
+ Incolla
+ Invia tasto control
+ Invia tasto fn
+
+ Finestra %1$d
+
+ Terminale in esecuzione
+
+ Terminale arrestato
+
+
+ Schermo
+
+ Barra di stato
+ Mostra o nascondi la barra di stato.
+ Barra di stato
+
+ Barra di azione
+ Scegli il comportamento della barra di azione (da Android 3.0 in su).
+ Comportamento barra di azione
+
+ Orientamento schermo
+ Scegli l\'orientamento dello schermo.
+ Orientamento schermo
+
+ Stile cursore
+ Scegli lo stile del cursore.
+ Stile cursore
+
+ Lampeggio cursore
+ Scegli il lampeggio del cursore.
+ Lampeggio cursore
+
+ Testo
+
+ UTF-8 predefinito
+ Attivazione predefinita della modalità UTF-8.
+
+ Dimensione carattere
+ Scegli l\'altezza dei caratteri in punti.
+ Dimensione carattere
+
+ Colori
+ Scegli il colore del testo.
+ Colore testo
+
+ Tastiera
+
+ Tasto Indietro
+ Scegli cosa fa la pressione del tasto Indietro.
+ Comportamento tasto Indietro
+
+ Tasto Control
+ Scegli il tasto Control.
+ Tasto Control
+
+ Tasto Fn
+ Scegli il tasto Fn.
+ Tasto Fn
+
+ Metodo inserimento
+ Scegli il metodo di inserimento per la tastiera.
+ Metodo inserimento
+
+ Shell
+ Linea di comando
+ Specifica la shell da usare.
+ Shell
+
+ Comando iniziale
+ Inviato alla shell all\'avvio.
+ Comando iniziale
+
+ Tipo terminale
+ Quale tipo di terminale da riportare sulla shell.
+ Tipo terminale
+
+ Chiudi finestra all\'uscita
+ Chiusura della finestra quando la sua shell termina.
+
+ Verifica voci PATH
+ Rimuovi cartelle inaccessibili da PATH.
+
+ Estensioni PATH
+ Permetti ad altre applicazioni di fornire comandi aggiuntivi (aggiungi a PATH).
+
+ Anteposizione PATH
+ Permetti ad altre applicazioni di sovrascrivere comandi esistenti (aggiungi all\'inizio di PATH).
+
+ Cartella HOME
+ Inserisci il percorso di una cartella scrivibile da usare come HOME.
+
+ Tasti Control e Fn
+
+ CTRLKEY spazio : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Nessun tasto Control impostato.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Su\nFNKEY A : Sinistra\nFNKEY S : Giù\nFNKEY D : Destra\nFNKEY P : Pag. Su\nFNKEY N : Pag. Giù\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Canc\nFNKEY I : Ins (Insert)\nFNKEY H : Home\nFNKEY F : Fine (End)\nFNKEY . : Control-\\\n
+ Nessun tasto Fn impostato.
+
+ Chiudi questa finestra?
+
+ Esegui comandi arbitrari nell\'Emulatore terminale
+ Permette all\'applicazione di aprire nuove finestre nell\'Emulatore terminale ed eseguire comandi in quelle finestre con tutti i permessi dell\'Emulatore, incluso l\'accesso ad internet e alla tua SD Card.
+ Aggiungi comandi all\'Emulatore terminale
+ Permette all\'applicazione di fornire comandi aggiuntivi (aggiungi cartelle a PATH) per l\'Emulatore terminale.
+ Sovrascrivi comandi nell\'Emulatore terminale
+ Permette all\'applicazione di sovrascrivere comandi esistenti con le proprie versioni (anteponi cartelle a PATH) nell\'Emulatore terminale.
+
+ Trascrivi dall\'Emulatore terminale
+ Trascrizione email usando:
+ Impossibile scegliere un\'attività email da inviare in trascrizione.
+
+ Tasto Alt invia ESC
+ Il tasto Alt invia ESC.
+ Il tasto Alt non invia ESC.
+
+ Invia eventi mouse
+ Click e scorrimento vengono inviati al terminale come sequenze di escape.
+
+ Usa scorciatoie da tastiera
+ Ctrl-Tab: scorri finestre, Ctrl-Shift-N: nuova finestra, Ctrl-Shift-V: incolla.
+ Scorciatoie da tastiera disabilitate.
+
+
diff --git a/term/src/main/res/values-iw/arrays.xml b/term/src/main/res/values-iw/arrays.xml
new file mode 100644
index 000000000..73d784d39
--- /dev/null
+++ b/term/src/main/res/values-iw/arrays.xml
@@ -0,0 +1,126 @@
+
+
+
+
+ הצג שורת המצב
+ הסתר שורת מצב
+
+
+ הצג תמיד שורת מצב
+ הסתר בר פעולות (הקש בחלק עליון של המסך או על כפתור תפריט כדי להציג)
+
+
+ אוטומטי
+ שוכב
+ עומד
+
+
+ סמן לא מהבהב
+ סמן מהבהב
+
+
+ מלבן
+ קו תחתון
+ קו ניצב
+
+
+ 4 * 8 פיקסלים
+ 6 נק\'
+ 7 נק\'
+ 8 נק\'
+ 9 נק\'
+ 10 נק\'
+ 12 נק\'
+ 14 נק\'
+ 16 נק\'
+ 20 נק\'
+ 24 נק\'
+ 28 נק\'
+ 32 נק\'
+ 36 נק\'
+ 42 נק\'
+ 48 נק\'
+ 64 נק\'
+ 72 נק\'
+ 96 נק\'
+ 144 נק\'
+ 288 נק\'
+
+
+ טקסט שחור על לבן
+ טקסט לבן על שחור
+ טקסט לבן על כחול
+ טקסט ירוק על שחור
+ טקסט אמבר על שחור
+ טקסט אדום על שחור
+ טקסט כחול הולו על שחור
+ Solarized Light
+ Solarized Dark
+ מסוף לינוקס
+
+
+ סוגר את חלונות מסוף
+ סוגר את חלון המסוף הנוכחי בלבד
+ סוגר פעילות, ומשאיר הפעלות רצות
+ שולח ESC למסוף
+ שולח TAB למסוף
+
+
+ רענון הכדור
+ \@ מפתח
+ מקש ALT שמאלי
+ מקש ALT ימני
+ מקש קול עליון
+ מקש קוך תחתון
+ לחצן מצלמה
+ ריק
+
+
+ רענון הכדור
+ \@ מפתח
+ מקש ALT שמאלי
+ מקש ALT ימני
+ מקש קול עליון
+ מקש קוך תחתון
+ לחצן מצלמה
+ ריק
+
+
+ מבוסס תווים
+ מבוסס מילים
+
+
+ כדור
+ \@
+ ALT-שמאלי
+ ALT-ימני
+ קול עליון
+ קול תחתון
+ מצלמה
+ ריק
+
+
+ כדור
+ \@
+ ALT-שמאלי
+ ALT-ימני
+ קול עליון
+ קול תחתון
+ מצלמה
+ ריק
+
+
diff --git a/term/src/main/res/values-iw/strings.xml b/term/src/main/res/values-iw/strings.xml
new file mode 100644
index 000000000..7f078ddcf
--- /dev/null
+++ b/term/src/main/res/values-iw/strings.xml
@@ -0,0 +1,117 @@
+
+
+
+ אמולטור מסוף
+ העדפות
+ חלון חדש
+ סגור חלון
+ חלונות
+ החלון הקודם
+ החלון הבא
+ איפוס המונח
+ דואר אלקטרוני ל
+ מקשים מיוחדים
+ מתג מקלדת מסך
+ מצב חלון המסוף אופס.
+ אל תכבה מסך
+ אפשר כיבוי מסך
+ אל תכבה WiFi
+ אפשר כיבוי WiFi
+ ערוך טקסט
+ בחר טקסט
+ העתק הכל
+ הדבק
+ שלח CTRL
+ שלח FN
+ חלון %1$d
+ מסוף פועל
+ מסוף הסתיים
+ מסך
+ שורת מצב
+ הצגת/הסתרת שורת מצב.
+ שורת מצב
+ בר הפעלות
+ בחר התנהגות של בר הפעלה (אנרואיד 3.0 ומעלה).
+ התנהגות בר הפעלה
+ כיוון מסך
+ בחר פעולה בשינוי כיוון מסך.
+ התנהגות כיוון מסך
+ סגנון הסמן
+ בחר סגנון לסמן.
+ סגנון הסמן
+ הבהוב הסמן
+ בחר הבהוב הסמן.
+ הבהוב הסמן
+ טקסט
+ ברירת מחדל UTF-8
+ האם UTF-8 מופעל כברירת מחדל.
+ גודל גופן
+ בחר גובה תווים בנקודות.
+ גודל גופן
+ צבעים
+ בחר בצע טקסט.
+ צבע טקסט
+ מקלדת
+ אופן הפעולה של לחצן החזרה
+ בחר פעולה בעת לחיצה על לחצן חזור.
+ אופן הפעולה של לחצן החזרה
+ מקש CTRL
+ בחר מקש CTRL.
+ מקש CTRL
+ מקש FN
+ בחר מקש FN.
+ מקש FN
+ שיטת קלט
+ בחר בשיטת הקלט עבור מקלדת מסך.
+ שיטת קלט
+ מעטפת
+ שורת הפקודה
+ ציין את שורת הפקודה.
+ מעטפת
+ פקודה ראשונית
+ נשלח בעת הפעלת shell.
+ פקודה ראשונית
+ סוג המסוף
+ איזה סוג מסוף לדווח ל-shell.
+ סוג המסוף
+ סגור חלון ביציאה
+ האם בסגירת shell חלון ייסגר.
+ אמת ערך PATH
+ האם תיקיות בלתי נגישות צריכות לרדת מ-PATH.
+ אפשר הרחבות PATH
+ האם יישומים אחרים יקבלו גישה להוסיף פקודות (להוסיף ל-PATH).
+ אפשר שינוי נתיב PATH
+ האם יישומים אחרים יוכלו לשנות פקודות קיימות (להוסיף לתחילת PATH).
+ תיקיית HOME
+ נתיב לתיקייה שתשומש כתיקיית HOME.
+ מקשי CTRL ו-FN
+ לא הוגדר מקש CTRL.
+ לא הוגדר מקש FN.
+ לסגור חלון זה?
+ הפעל פקודות שרירותיות במסוף
+ הוסף פקודות למסוף
+ דרוס פקודות ב-Terminal Emulator
+ תעתיק של אימולטור מסוף באנדרואיד
+ תעתיק מייל באמצעות:
+ לא הצליחתי למצוא לקוח מייל כדי לשלוח תעתיק.
+ ALT שולח ESC
+ ALT שולח ESC.
+ מקש ALT לא שולח ESC.
+ שלח אירועי עכבר
+ השתמש בקיצורי מקלדת
+ קיצורי מקלדת מושבתים.
+
diff --git a/term/src/main/res/values-ja/arrays.xml b/term/src/main/res/values-ja/arrays.xml
new file mode 100644
index 000000000..42a3e3ae4
--- /dev/null
+++ b/term/src/main/res/values-ja/arrays.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+ ステータスバーを表示する
+ ステータスバーを隠す
+
+
+
+
+ 常にアクションバーを表示する
+ アクションバーを隠す (スクリーンの上部へのタッチかメニューキーで表示)
+
+
+
+ 点滅しないカーソル
+ 点滅するカーソル
+
+
+
+ 四角
+ 下線
+ 縦線
+
+
+
+ 4 x 8ピクセル
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ 白背景に黒字
+ 黒背景に白字
+ 青背景に白字
+ 黒背景に緑字
+ 黒背景にアンバー字
+ 黒背景に赤字
+ 黒背景にホロブルー字
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ すべてのターミナルウインドウを閉じる
+ このターミナルウインドウだけを閉じる
+ アクティビティを閉じ,セッションは実行を継続
+ ターミナルにESCを送出
+ ターミナルにTABを送出
+
+
+
+ トラックボール
+ \@キー
+ 左Altキー
+ 右Altキー
+ ボリュームアップ
+ ボリュームダウン
+ カメラキー
+ なし
+
+
+
+ トラックボール
+ \@キー
+ 左Altキー
+ 右Altキー
+ ボリュームアップ
+ ボリュームダウン
+ カメラキー
+ なし
+
+
+
+ 文字ベース
+ 単語ベース
+
+
+
+
+ ボール
+ \@
+ 左Alt
+ 右Alt
+ ボリュームアップ
+ ボリュームダウン
+ カメラ
+ なし
+
+
+
+
+ ボール
+ \@
+ 左Alt
+ 右Alt
+ ボリュームアップ
+ ボリュームダウン
+ カメラ
+ なし
+
+
diff --git a/term/src/main/res/values-ja/strings.xml b/term/src/main/res/values-ja/strings.xml
new file mode 100644
index 000000000..671cce0bb
--- /dev/null
+++ b/term/src/main/res/values-ja/strings.xml
@@ -0,0 +1,152 @@
+
+
+
+ 端末エミュレータ
+ 設定
+ 新規ウインドウ
+ ウインドウを閉じる
+ ウインドウ一覧
+ 前のウインドウ
+ 次のウインドウ
+ 端末をリセット
+ メール送信
+ 特殊キー
+ ソフトキーボード
+
+ このウインドウのターミナル状態はリセットされました.
+
+ WakeLockを取得
+ WakeLockを解除
+ WifiLockを取得
+ WifiLockを解除
+
+ テキスト編集
+ テキスト選択
+ すべてコピー
+ 貼付け
+ コントールキー送信
+ fnキー送信
+
+ ウインドウ %1$d
+
+ ターミナルセッション実行中
+
+ ターミナルセッション終了
+
+
+ スクリーン
+
+ ステータスバー
+ ステータスバーの表示/非表示
+ ステータスバー
+
+ アクションバー
+ アクションバーの振舞いを選択する. (Android 3.0 以降).
+ アクションバーの振舞い
+
+ カーソルのスタイル
+ カーソルスタイルの選択
+ カーソルスタイル
+
+ カーソルの点滅
+ カーソルの点滅を選択
+ カーソルの点滅
+
+ テキスト
+
+ デフォルトをUTF-8にする
+ デフォルトでUTF-8モードを可能にするか否か.
+
+ フォントサイズ
+ 文字の高さと大きさを選択
+ フォントサイズ
+
+ 色
+ 文字の色を選択
+ 文字の色
+
+ キーボード
+
+ Backボタンの振舞い
+ Backボタンの振る舞いを選択
+ Backボタンの振舞い
+
+ コントロールキー
+ コントロールキーを選択
+ コントロールキー
+
+ Fnキー
+ Fnキーを選択
+ Fnキー
+
+ インプットメソッド
+ ソフトキーボードのインプットメソッドを選択
+ インプットメソッド
+
+ シェル
+ コマンドライン
+ コマンドラインシェルを指定
+ シェル
+
+ 初期コマンド
+ 開始時、シェルにコマンドを送信
+ 初期コマンド
+
+ ターミナルタイプ
+ シェルに報告するターミナルタイプ
+ ターミナルタイプ
+
+ 終了時にウインドウを閉じる
+ シェル終了時にウインドウを閉じるか否か.
+
+ PATHエントリを照合
+ アクセス不可ディレクトリをPATHから取り除くべきか否か.
+
+ PATH拡張の許可
+ 他のアプリから追加のコマンドの提供を許可するか否か (PATHへの追加).
+
+ PATHへのプリペンドの許可
+ 他のアプリから既存のコマンドの上書きを許可するか否か (PATHの先頭に追加).
+
+ コントロールキーとファンクションキー
+
+ CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ No control key set.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ ファンクションキーの設定がありません.
+
+ このウインドウを閉じますか?
+
+ 端末エミュレータで任意のコマンドを実行
+ アプリケーションに対して,端末エミュレータに新規ウインドウを開かせ,インターネットやSDカードへのアクセスを含む,端末エミュレータが持つすべてのパーミッションでコマンドを実行することを許可する.
+ 端末エミュレータへのコマンド追加
+ アプリケーションに対して,端末エミュレータのコマンドを追加する(PATHにディレクトリを追加する)ことを許可する.
+ 端末エミュレータのコマンドを上書きする
+ アプリケーションに対して,端末エミュレータが持つ既存のコマンドを,アプリケーション独自のバージョンに置き換える(PATHの先頭にディレクトリを追加する)ことを許可する.
+
+ Transcript from Terminal Emulator for Android
+ メール転写の選択:
+ 転写を送信するEメール用アクティビティが選択できませんでした.
+
+ AltキーでESCを送出
+ Alt keyでESCを送出する.
+ Alt keyでESCを送出しない.
+
+
diff --git a/term/src/main/res/values-ka/arrays.xml b/term/src/main/res/values-ka/arrays.xml
new file mode 100644
index 000000000..214c2deca
--- /dev/null
+++ b/term/src/main/res/values-ka/arrays.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+ სტატუსის ხაზის ჩვენება
+ სტატუსის ხაზის დამალვა
+
+
+
+
+ სტატუსის ხაზის მიმაგრება ეკრანზე
+ სტატუსის ხაზის დამალვა(საჩვენებლად შეეხეთ ეკრანის ზედა ნაწილს ან მენიუს ღილაკს)
+
+
+
+ არა-მოციმციმე კურსორი
+ მოციმციმე კურსორი
+
+
+
+ ოთხკუთხედი
+ ქვედა ტირე
+ ვერტიკალური ხაზი
+
+
+
+ 4 x 8 პიქსელი
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ შავი თეთრზე
+ თეთრი შავზე
+ თეთრი ლურჯზე
+ მწვანე შავზე
+ ქარვისფერი შავზე
+ წითელი შავზე
+ ღია ლურჯი შავზე
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ ტერმინალის ყველა ფანჯრის დახურვა
+ ტერმინალის მონიშნული ფანჯრის დახურვა
+ ფანჯრის დახურვა სესიების გაშვებულ მდგომარეობაში დატოვებით
+ ტერმინალისათვის ESC-ის გაგზავნა
+ ტერმინალისათვის TAB-ის გაგზავნა
+
+
+
+ Jog ball
+ \@ ღილაკი
+ მარცხენა Alt ღილაკი
+ მარჯვენა Alt ღილაკი
+ ხმის აწევის ღილაკი
+ ხმის დაწევის ღილაკი
+ კამერის ღილაკი
+ არცერთი
+
+
+
+ Jog ball
+ \@ ღილაკი
+ მარცხენა Alt ღილაკი
+ მარჯვენა Alt ღილაკი
+ ხმის აწევის ღილაკი
+ ხმის დაწევის ღილაკი
+ ამერის ღილაკი
+ არცერთი
+
+
+
+ სიმბოლოებზე დამოკიდებული
+ სიტყვებზე დამოკიდებული
+
+
+
+
+ ბურთი
+ \@
+ მარცხ.-Alt
+ მარჯვ.-Alt
+ ხმა-ზემოთ
+ ხმა-ქვემოთ
+ კამერა
+ არცერთი
+
+
+
+
+ ბურთი
+ \@
+ მარცხ.-Alt
+ მარჯვ.-Alt
+ ხმა-ზემოთ
+ ხმა-ქვემოთ
+ კამერა
+ არცერთი
+
+
diff --git a/term/src/main/res/values-ka/strings.xml b/term/src/main/res/values-ka/strings.xml
new file mode 100644
index 000000000..f03e1226b
--- /dev/null
+++ b/term/src/main/res/values-ka/strings.xml
@@ -0,0 +1,143 @@
+
+
+
+ Terminal Emulator
+ გამართვა
+ ახალი ფანჯარა
+ ფანჯრის დახურვა
+ ფანჯრები
+ წინა ფანჯარა
+ შემდეგი ფანჯარა
+ ტერმინალის რესტარტი
+ ელ-ფოსტის გაგზავნა
+ სპეციალური ღილაკები
+ პროგრამული კლავიატურის ჩართ./გამორთ.
+
+ ამ ტერმინალის მდგომარეობა საწყისზე დაბრუნდა.
+
+ WakeLock-ის ჩართვა
+ WakeLock-ის გამორთვა
+ WifiLock-ის ჩართვა
+ WifiLock-ის გამორთვა
+
+ ტექსტის ჩასწორება
+ ტექსტის მონიშვნა
+ ასლი
+ ჩასმა
+ საკონტროლო ღილაკის გაგზავნა
+ fn ღილაკის გაგზავნა
+
+ ფანჯარა %1$d
+
+ ტერმინალის სესია გაშვებულია
+
+ ტერმინალის სესია დასრულდა
+
+
+ ეკრანი
+
+ მდგომარეობის ხაზი
+ მდგომარეობის ხაზის ჩართვა/გამორთვა.
+ მდგომარეობის ხაზი
+
+ მოქმედების ხაზი
+ აირჩიეთ მოქმედების ხაზის ქცევის პარამეტრები(Android 3.0 და ზემოთ).
+ მოქმედების ხაზის ქცევა
+
+ კურსორის სტილი
+ აირჩიეთ კურსორის სტილი.
+ კურსორის სტილი
+
+ კურსორის ციმციმი
+ აირჩიეთ კურსორის ციმციმის ტიპი.
+ კურსორის ციმციმი
+
+ ტექსტი
+
+ UTF-8-ის გამოყენება
+ როდესაც UTF-8 გამოიყენება ნაგულისხმევად.
+
+ ფონტის ზომა
+ აირჩიეთ სიმბოლოს სიმაღლე წერტილებში.
+ ფონტის ზომა
+
+ ფერები
+ აირჩიეთ ტექსტის ფერი.
+ ტექსტის ფერი
+
+ კლავიატურა
+
+ ღილაკ Back-ის ქცევა
+ აირჩიეთ რას აკეთებს ღილაკი back.
+ ღილაკ Back-ის ქცევა
+
+ ღილაკი Control
+ აირჩიეთ ღილაკი control.
+ ღილაკი Control
+
+ ღილაკი Fn
+ აირჩიეთ ღილაკი Fn.
+ ღილაკი Fn
+
+ შეყვანის მეთოდი
+ აირჩიეთ შეყვანის მეთოდი პროგრამული კლავიატურისათვის.
+ შეყვანის მეთოდი
+
+ გარსი
+ ბრძანების ხაზი
+ აირჩიეთ გარსის ბრძანების ხაზი.
+ გარსი
+
+ საწყისი ბრძანება
+ გაეგზავნოს გარსს მისი გაშვებისას.
+ საწყისი ბრძანება
+
+ ტერმინალის ტიპი
+ ტერმინალის რომელი ტიპი იქნება მიწოდებული გარსისათვის.
+ ტერმინალის ტიპი
+
+ ფანჯრის დახურვა გასვლისას
+ უნდა დაიხუროს თუ არა ფანჯარა გარსიდან გამოსვლის დროს.
+
+ გარსის ცვლად PATH-ის გადამოწმება
+ უნდა იყოს თუ არა ამოღებული PATH-დან მიუწვდომელი დასტები.
+
+ PATH-ის გავრცობის ნების დართვა
+ უნდა იყვნენ თუ არა აპლიკაციები ნებადართული, რომ შეცვალონ გარსის ცვლადი PATH.
+
+ PATH-ის დასაწყისში ჩამატება
+ უნდა იყვნენ თუ არა აპლიკაციები ნებადართული, რომ შეცვალონ გარსის ცვლადი PATH მის დასაწყისში დასტების ჩამატებით.
+
+ Control და Function ღილაკები
+
+ CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ ღილაკი Control მითითებული არაა.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ ღილაკი Function მითითებული არაა.
+
+ გნებავთ ფანჯრის დახურვა?
+
+ ტერმინალში სასურველი ბრძანებების გაშვება
+ Allows application to open new windows in Terminal Emulator for Android and run commands in those windows with all of Terminal Emulator for Android\'s permissions, including access to the Internet and your SD Card.
+ Terminal Emulator-სათვის ბრძანებების დამატება
+ Allows application to provide additional commands (add directories to the PATH) for Terminal Emulator for Android.
+ Terminal Emulator-ის ბრძანებების უგულებელყოფა
+ Allows application to override existing commands with its own versions (prepend directories to the PATH) in Terminal Emulator for Android.
+
diff --git a/term/src/main/res/values-ko/arrays.xml b/term/src/main/res/values-ko/arrays.xml
new file mode 100644
index 000000000..e67fad05e
--- /dev/null
+++ b/term/src/main/res/values-ko/arrays.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+ 상단바 보이기
+ 상단바 숨기기
+
+
+
+
+ 액션바 보이기
+ 액션바 숨기기 (화면 상단을 누르거나 메뉴키를 누르면 반투명하게 표시됩니다.)
+
+
+
+ 자동
+ 가로
+ 세로
+
+
+
+ 깜박이지 않음
+ 깜박임
+
+
+
+ 직사각형
+ 밑줄
+ 세로줄
+
+
+
+ 4 x 8 픽셀
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ 하얀 배경에 검은색 글자
+ 검은 배경에 하얀 글자
+ 파란 배경에 하얀 글자
+ 검은 배경에 초록색 글자
+ 검은 배경에 황색 글자
+ 검은 배경에 빨간 글자
+ 검은 배경에 Holo Blue 글자
+ 밝은 솔라리스
+ 어두운 솔라리스
+
+
+
+ 모든 터미널 창 닫기
+ 현재 터미널 창만 닫기
+ 어플리케이션 및 세션 종료
+ ESC키 입력
+ TAB키 입력
+
+
+
+ 트랙볼
+ \@ 키
+ 왼쪽 Alt 버튼
+ 오른쪽 Alt 버튼
+ 볼륨 상 버튼
+ 볼륨 하 버튼
+ 카메라 버튼
+ 없음
+
+
+
+ 트랙볼
+ \@ 키
+ 왼쪽 Alt 버튼
+ 오른쪽 Alt 버튼
+ 볼륨 상 버튼
+ 볼륨 하 버튼
+ 카메라 버튼
+ 없음
+
+
+
+ 문자 기반
+ 단어 기반
+
+
+
+
+ 트랙볼
+ \@키
+ 왼쪽 Alt
+ 오른쪽 Alt
+ 볼륨 상
+ 볼륨 하
+ 카메라 키
+ 없음
+
+
+
+
+ 트랙볼
+ \@키
+ 왼쪽 Alt
+ 오른쪽 Alt
+ 볼륨 상
+ 볼륨 하
+ 카메라 키
+ 없음
+
+
diff --git a/term/src/main/res/values-ko/strings.xml b/term/src/main/res/values-ko/strings.xml
new file mode 100644
index 000000000..52660f74f
--- /dev/null
+++ b/term/src/main/res/values-ko/strings.xml
@@ -0,0 +1,159 @@
+
+
+
+ 터미널 에뮬레이터
+ 환경 설정
+ 새로운 창
+ 창 닫기
+ 창 목록
+ 이전 창
+ 다음 창
+ 터미널 초기화
+ 이메일 보내기
+ 특수 키
+ 가상 키보드 전환
+
+ 이 창의 터미널 상태를 초기화하였습니다.
+
+ WakeLock 활성화
+ WakeLock 해제
+ Wi-Fi 잠금 활성화
+ Wi-Fi 잠금 해제
+
+ 수정
+ 선택
+ 모두 복사
+ 붙여넣기
+ Ctrl키 입력
+ Fn키 입력
+
+ 명령창 %1$d
+
+ 터미널 세션이 실행중입니다.
+
+ 터미널 세션을 마쳤습니다.
+
+
+ 화면
+
+ 상단바
+ 상단바를 숨기거나 보입니다.
+ 상단바 설정
+
+ 액션바
+ 액션바의 상태를 설정합니다.
+ 액션바 상태
+
+ 화면 회전
+ 터미널 창의 회전 상태를 설정합니다.
+ 화면 회전 상태
+
+ 커서 모양
+ 커서의 모양을 설정합니다.
+ 커서 모양
+
+ 커서 깜박임
+ 커서의 깜박임을 설정합니다.
+ 커서 깜빡임
+
+ 글자
+
+ UTF-8 인코딩 사용
+ UTF-8 인코딩을 기본으로 사용할지 설정합니다.
+
+ 글자 크기
+ 글자의 크기를 설정합니다.
+ 글자 크기 선택
+
+ 글자 색깔
+ 글자 색깔을 설정합니다.
+ 글자 색깔
+
+ 키보드
+
+ 뒤로가기 버튼 행동
+ 뒤로가기 버튼을 눌렀을 때 행동을 설정합니다.
+ 뒤로가기 버튼 행동
+
+ Ctrl키 (컨트롤키)
+ 컨트롤키를 설정합니다.
+ Ctrl키 설정
+
+ Fn키 (Function키)
+ Fn키를 설정합니다.
+ Fn키
+
+ 입력 방식
+ 가상 키보드의 입력 방식을 설정합니다.
+ 입력 방식
+
+ 명령줄
+ 명령줄
+ 사용자 지정 명령줄을 설정합니다.
+ 명령줄
+
+ 초기 명령어
+ 터미널이 실행될 때 실행될 명령어를 선택합니다.
+ 초기 명령어
+
+ 터미널 종류
+ 터미널의 종류를 선택합니다.
+ 타미널 종류
+
+ 종료 시에 창 닫기
+ 터미널 프로세스가 종료되면 활성화된 창을 닫습니다.
+
+ PATH 경로 확인
+ 액세스할 수 없는 경로가 PATH에서 제거되어야할지 확인합니다.
+
+ PATH 확장 허용
+ 다른 어플리케이션에서 추가적인 경로를 PATH에 생성할 수 있는지 설정합니다.
+
+ PATH 경로 덮어쓰기 허용
+ 다른 어플리케이션에서 이미 존재하는 경로를 덮어씌울 수 있는지 설정합니다.
+
+ HOME 경로
+ HOME으로 설정 가능한 경로 (쓰기 가능한 경로)를 설정합니다.
+
+ Ctrl키와 Fn키
+
+ CTRLKEY 스페이스바 : Control-@ (공백 (NULL))\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Ctrl키가 설정되지 않았습니다.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : 위쪽 버튼 (↑)\nFNKEY A : 왼쪽 버튼 (←)\nFNKEY S : 아래쪽 버튼 (↓)\nFNKEY D : 오른쪽 버튼 (→)\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : 탭\nFNKEY L : | (세로줄)\nFNKEY U : _ (밑줄)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ Fn키가 설정되지 않았습니다.
+
+ 이 창을 닫으시겠습니까?
+
+ 터미널 에뮬레이터에서 명령어 실행
+ 어플리케이션이 터미널 에뮬레이터에서 새로운 창을 열 수 있게 허용하고, 터미널 에뮬레이터의 모든 권한을 이용하여 명령을 실행할 수 있습니다. 예를 들어, SD카드나 인터넷에 액세스할 수 있습니다.
+ 터미널 에뮬레이터에서 명령어 추가
+ 어플리케이션이 터미널 에뮬레이터에서 사용할 수 있는 명령어를 만들고, 그 명령어를 관리할 수 있게 합니다.
+ 터미널 에뮬레이터에서 명령어 덮어쓰기
+ 어플리케이션이 터미널 에뮬레이터의 명령어를 덮어씌울 수 있습니다.
+
+ 터미널 에뮬레이터로 이메일 보내기
+ 이메일 보내기 :
+ 이메일을 보낼 수 있는 어플리케이션이 존재하지 않습니다.
+
+ Alt 키로 ESC 키를 입력
+ Alt 키가 ESC 키를 입력합니다.
+ Alt 키는 ESC 키를 입력하지 않습니다.
+
+
diff --git a/term/src/main/res/values-nb/arrays.xml b/term/src/main/res/values-nb/arrays.xml
new file mode 100644
index 000000000..c7d6d60a9
--- /dev/null
+++ b/term/src/main/res/values-nb/arrays.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+ Vis statuslinje
+ Skjul statuslinje
+
+
+
+
+ Alltid vis handlingslinjen
+ Skjul handlingslinjen (Trykk på toppen av skjermen for å vise den)
+
+
+
+ Automatisk
+ Landskap
+ Portrett
+
+
+
+ Ikke-blinkende peker
+ Blinkende peker
+
+
+
+ Rektangulær
+ Understreket
+ Vertikal linje
+
+
+
+ 4x8 Piksler
+ 6 Punkter
+ 7 Punkter
+ 8 Punkter
+ 9 Punkter
+ 10 Punkter
+ 12 Punkter
+ 14 Punkter
+ 16 Punkter
+ 20 Punkter
+ 24 Punkter
+ 28 Punkter
+ 32 Punkter
+ 36 Punkter
+ 42 Punkter
+ 48 Punkter
+ 64 Punkter
+ 72 Punkter
+ 96 Punkter
+ 144 Punkter
+ 288 Punkter
+
+
+
+ Sort tekst på hvit
+ Hvit tekst på sort
+ Hvit tekst på blå
+ Grønn tekst på sort
+ Orange tekst på sort
+ Rød tekst på sort
+ Holo blå på sort
+ Solarized Lys
+ Solarized Mørk
+ Linux konsoll
+
+
+
+ Lukker alle terminalvinduer
+ Lukker kun dette terminalviduet
+ Lukker aktiviteten, men lar økten kjøre
+ Sender ESC til terminalen
+ Sender TAB til terminalen
+
+
+
+ Trackball
+ \@-tast
+ Venstre Alt-tast
+ Høyre Alt-tast
+ Vol opp
+ Vol ned
+ Kamera-tast
+ Ingen
+
+
+
+ Trackball
+ \@-tast
+ Venstre Alt-tast
+ Høyre Alt-tast
+ Vol opp
+ Vol ned
+ Kamera-tast
+ Ingen
+
+
+
+ Tegnbasert
+ Ordbasert
+
+
+
+
+ Ball
+ \@
+ Venstre-Alt
+ Høyre-Alt
+ Vol-Opp
+ Vol-Ned
+ Kamera
+ Ingen
+
+
+
+
+ Ball
+ \@
+ Venstre-Alt
+ Høyre-Alt
+ Vol-Opp
+ Vol-Ned
+ Kamera
+ Ingen
+
+
diff --git a/term/src/main/res/values-nb/strings.xml b/term/src/main/res/values-nb/strings.xml
new file mode 100644
index 000000000..c6731f955
--- /dev/null
+++ b/term/src/main/res/values-nb/strings.xml
@@ -0,0 +1,161 @@
+
+
+
+ Terminalemulator
+ Innstillinger
+ Nytt vindu
+ Lukk vindu
+ Vinduer
+ Forrige vindu
+ Neste vindu
+ Tilbakestill terminal
+ Send epost til
+ Spesielle tegn
+ Veksle virtuelt tastatur
+
+ Terminalen i dette vinduet ble tilbakestilt.
+
+ Ta WakeLock
+ Dropp WakeLock
+ Ta WifiLock
+ Dropp WifiLock
+
+ Rediger tekst
+ Marker tekst
+ Kopier alt
+ Lim inn
+ Send ctrl-tast
+ Send fn-ast
+
+ Vindu %1$d
+
+ Terminaløkt kjører
+
+ Terminaløkt ferdig
+
+
+ Skjerm
+
+ Statuslinje
+ Vis/Skjul statuslinje
+ Statuslinje
+
+ Handlingslinje
+ Velg hvordan handlingslinjen (ActionBar) skal oppføre seg (Android 3.0 og oppover).
+ Handlingslinje
+
+ Rotasjon
+ Velg hvordan rotasjon av skjermen skal håndteres.
+ Rotasjon
+
+ Pekertype
+ Velg pekertype.
+ Pekertype
+
+ Blinkende peker
+ Velg blinkende peker
+ Blinkende peker
+
+ Tekst
+
+ Aktiver UTF-8
+ Velg om UTF-8 skal være aktivert som standard.
+
+ Skriftstørrelse
+ Velg tegnhøyde i antall punkter.
+ Skriftstørrelse
+
+ Farger
+ Velg tekstfarge.
+ Tekstfarge
+
+ Tastatur
+
+ Tilbaketast
+ Velg hva som skal skje når det trykkes på tilbaketasten.
+ Tilbaketast
+
+ Ctrl-tast
+ Velg Ctrl-tast
+ Ctrl-tast
+
+ Fn-tast
+ Velg Fn-tast.
+ Fn-tast
+
+ Inndatametode
+ Velg inndatametode for virtuelt tastatur.
+ Inndatametode
+
+ Skall
+ Kommandolinje
+ Velg kommandolinjeskall.
+ Skall
+
+ Innledende kommando
+ Sendes til skallet når det starter.
+ Innledende kommando
+
+ Terminaltype
+ Velg terminaltype.
+ Terminaltype
+
+ Lukk vinduet ved avslutning
+ Velg om vinduet skal lukkes når skallet avsluttes.
+
+ Kontroller oppføringer i PATH
+ Velg om utilgjengelige mapper skal fjernes fra PATH.
+
+ Tillat utvidelse av PATH
+ Velg om andre apper skal kunne tilby flere kommandoer (legge til i PATH).
+
+ Tillat overstyring av PATH
+ Velg om andre apper skal kunne overstyre eksisterende kommandoer (legge til i begynnelsen av PATH).
+
+ HOME-mappe
+ Sti til en skrivbar mappe som skal brukes som HOME.
+
+ Ctrl og fn-tast
+
+ CTRLKEY Mellomrom : Ctrl-@ (NUL)\nCTRLKEY A..Z : Ctrl-A..Z\nCTRLKEY 5 : Ctrl-]\nCTRLKEY 6 : Ctrl-^\nCTRLKEY 7 : Ctrl-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Ingen ctrl-tast valgt
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Opp\nFNKEY A : Venstre\nFNKEY S : Ned\nFNKEY D : Høyre\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (understrek)\nFNKEY E : Ctrl-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Ctrl-\\\n
+ Ingen fn-tast valgt
+
+ Lukk dette vinduet?
+
+ Kjøre forskjellige kommandoer i Terminalemulatoren
+ Lar appen åpne nye vinduer i Terminalemulatoren, samt kjøre kommandoer i disse. Appen vil da kunne kjøre kommandoer med alle tillatelsene som Terminalemulatoren har, inkludert tilgang til Internett og SD-kortet.
+ Legge til kommandoer i Terminalemulatoren
+ Lar appen tilby flere kommandoer (Legge til i PATH) til Terminalemulatoren.
+ Overstyr kommandoer i Terminalemulatoren
+ Lar appen overstyre eksisterende kommandoer med sine egene versjoner (legge til i begynnelsen av PATH) i Terminalemulatoren
+
+ Transcript fra Android Terminalemulator
+ Mail transcript med:
+ Kunne ikke velge aktivitet for å sende transcript.
+
+ Alt-tasten sender ESC
+ Alt-tasten sender ESC.
+ Alt-tasten sender ikke ESC.
+
+ Send musebevegelser
+ Velg om trykk og scroll-sekvenser skal bli sendt som escape-sekvenser til terminalen.
+
diff --git a/term/src/main/res/values-nl/arrays.xml b/term/src/main/res/values-nl/arrays.xml
new file mode 100644
index 000000000..b55394e35
--- /dev/null
+++ b/term/src/main/res/values-nl/arrays.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+ Statusbalk tonen
+ Statusbalk verbergen
+
+
+
+ Menubalk altijd tonen
+ Menubalk verbergen (op bovenkant scherm of menuknop tikken om te tonen)
+
+
+
+ Automatisch
+ Liggend
+ Staand
+
+
+
+ Geen knipperende cursor
+ Knipperende cursor
+
+
+
+ Rechthoek
+ Onderlijnd
+ Verticale balk
+
+
+
+ 4 x 8 pixels
+ 6 pnt.
+ 7 pnt.
+ 8 pnt.
+ 9 pnt.
+ 10 pnt.
+ 12 pnt.
+ 14 pnt.
+ 16 pnt.
+ 20 pnt.
+ 24 pnt.
+ 28 pnt.
+ 32 pnt.
+ 36 pnt.
+ 42 pnt.
+ 48 pnt.
+ 64 pnt.
+ 72 pnt.
+ 96 pnt.
+ 144 pnt.
+ 288 pnt.
+
+
+
+ Zwarte tekst op wit
+ Witte tekst op zwart
+ Witte tekst op blauw
+ Groene tekst op zwart
+ Oranje tekst op zwart
+ Rode tekst op zwart
+ Holo-blauwe tekst op zwart
+ Gesolariseerd licht
+ Gesolariseerd donker
+ Linux console
+
+
+
+ Alle terminalvensters sluiten
+ Alleen huidige terminalvensters sluiten
+ App sluiten, sessies open laten
+ ESC naar terminal sturen
+ TAB naar terminal sturen
+
+
+
+ Trackball
+ \@-knop
+ Linker Alt-knop
+ Rechter Alt-knop
+ Volume omhoog-knop
+ Volume omlaag-knop
+ Cameraknop
+ Geen
+
+
+
+ Trackball
+ \@-knop
+ Linker Alt-knop
+ Rechter Alt-knop
+ Volume omhoog-knop
+ Volume omlaag-knop
+ Cameraknop
+ Geen
+
+
+
+ Karaktergebaseerd
+ Woordgebaseerd
+
+
+
+
+ Ball
+ \@
+ Alt-links
+ Alt-rechts
+ Volume+
+ Volume-
+ Camera
+ Geen
+
+
+
+
+ Ball
+ \@
+ Alt-links
+ Alt-rechts
+ Volume+
+ Volume-
+ Camera
+ Geen
+
+
diff --git a/term/src/main/res/values-nl/strings.xml b/term/src/main/res/values-nl/strings.xml
new file mode 100644
index 000000000..d342b71af
--- /dev/null
+++ b/term/src/main/res/values-nl/strings.xml
@@ -0,0 +1,171 @@
+
+
+
+ Terminal
+ Instellingen
+ Nieuw venster
+ Venster sluiten
+ Vensters
+ Vorig venster
+ Volgend venster
+ Herstellen
+ E-mail sturen naar
+ Speciale knoppen
+ Toetsenbord aan/uit
+
+ Slaapblokkering aan
+ Slaapblokkering uit
+ Wifi-blokkering aan
+ Wifi-blokkering uit
+
+ Tekst wijzigen
+ Selecteer tekst
+ Alles kopiëren
+ Plakken
+
+ Terminalsessie is actief
+
+
+ Scherm
+
+ Statusbalk
+ Statusbalk tonen/verbergen
+ Statusbalk
+
+ Menubalk
+ Gedrag van menubalk kiezen (Android 3.0 en hoger)
+ Gedrag menubalk
+
+ Oriëntatie van het scherm
+ Oriëntatie van het scherm kiezen
+ Oriëntatie van het scherm
+
+ Cursorstijl
+ Cursorstijl kiezen
+ Cursorstijl
+
+ Cursorblink
+ Cursorblink kiezen
+ Cursorblink
+
+ Tekst
+
+ UTF-8
+ UTF-8 standaard inschakelen
+
+ Lettergrootte
+ Karakterhoogte in punten kiezen
+ Lettergrootte
+
+ Kleuren
+ Tekstkleur kiezen
+ Tekstkleur
+
+ Toetsenbord
+
+ Terugknop
+ Bepalen wat de terugknop doet
+ Gedrag terugknop
+
+ Control-knop
+ Control-knop kiezen
+ Control-knop
+
+ Fn-knop
+ Functieknop kiezen
+ Fn-knop
+
+ Invoermethode
+ Invoermethode voor toetsenbord kiezen
+ Invoermethode
+
+ Shell
+ Opdrachtregel
+ Opdrachtregel-shell opgeven
+ Shell
+
+ Beginopdracht
+ Wordt naar de shell verzonden bij opstarten
+ Beginopdracht
+
+ Soort terminal
+ Welk soort terminal te melden aan de shell
+ Soort terminal
+
+ Venster sluiten bij exit
+ Venster sluiten als de shell afgesloten wordt
+
+ PATH entries controleren
+ Controleren of ontoegankelijk directories verwijderd moeten worden uit het PATH
+
+ PATH-extensies toestaan
+ Andere apps toestaan om extra opdrachten te verstrekken (aan PATH toevoegen)
+
+ PATH prepend toestaan
+ Andere apps toestaan om bestaande opdrachten te overschrijven (voor PATH invoegen).
+
+ HOME folder
+ Pad naar een beschrijfbare map te gebruiken als HOME
+
+ Control- en functieknoppen
+
+ CTRLKEY spatie : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Geen controlknop ingesteld
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Omhoog\nFNKEY A : Links\nFNKEY S : Omlaag\nFNKEY D : Rechts\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pijpje)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ Geen functieknop ingesteld
+
+ Dit venster sluiten?
+
+ Gegevens uit Terminal Emulator for Android
+ Gegevens e-mailen via:
+ Kon geen e-mail activity kiezen om gegevens te sturen
+
+ Alt-knop stuurt ESC
+ Alt-knop stuurt ESC
+ Alt-knop stuurt geen ESC
+
+ Muisgebeurtenissen sturen
+ Tikken en scrollen naar de terminal sturen als \'escape sequences\'
+
+ Sneltoesten gebruiken
+ Ctrl-Tab: venster wisselen, Ctrl-Shift-N: nieuw venster, Ctrl-Shift-V: plakken
+ Sneltoesten zijn uitgeschakeld
+
+ Terminal
+
+ "Terminal snelkoppeling"
+
+ BESTANDSKIEZER
+ Externe opslag niet beschikbaar
+ Of voer hier pad in
+ Thema veranderen
+
+ opdracht
+ --voorbeeld=\""a\"
+ Opdracht vinden
+ DOEL SNELKOPPELING KIEZEN
+ Opdrachtvenster vereist volledig pad, zonder toevoegingen. Voor extra opdrachten, gebruikt het veld \'Toevoeging\' (bijv: cd /sdcard).
+ Toevoeging\:
+ Naam snelkoppeling\:
+ Tekstpictogram
+ TEKSTPICTOGRAM MAKEN
+ Terminal snelkoppeling
+
+ VAST
+ Pictogramtekst
+
diff --git a/term/src/main/res/values-pl/arrays.xml b/term/src/main/res/values-pl/arrays.xml
new file mode 100644
index 000000000..e9cec774e
--- /dev/null
+++ b/term/src/main/res/values-pl/arrays.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+ Pokaż pasek stanu
+ Ukryj pasek stanu
+
+
+
+
+ Zawsze pokazuj pasek narzędziowy
+ Ukryj pasek narzędzi (dotknij góry ekranu lub przycisk Menu, aby wyświetlić)
+
+
+
+ Niemigający kursor
+ Migający kursor
+
+
+
+ Prostokąt
+ Podkreślenie
+ Poziomy pasek
+
+
+
+ 4 x 8 pixeli
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Czarny tekst na białym tle
+ Biały tekst na czarnym tle
+ Biały tekst na niebieskim tle
+ Zielony tekst na czarnym tle
+ Bursztynowy tekst na czarnym tle
+ Czerwony tekst na czarnym tle
+ Błękitny tekst na czarnym tle
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Zamyka wszystkie okna terminala
+ Zamyka tylko aktualne okno terminala
+ Ukrywa program, pozostawiając sesję w tle
+ Wysyła klawisz ESC do terminala
+ Wysyła klawisz TAB do terminala
+
+
+
+ Kulka/tackball
+ Przycisk \@
+ Lewy Alt
+ Prawy Alt
+ Przycisk zwiększania głośności
+ Przycisk zmniejszania głośności
+ Przycisk aparatu
+ Żaden
+
+
+
+ Kulka/tackball
+ Przycisk \@
+ Lewy Alt
+ Prawy Alt
+ Przycisk zwiększania głośności
+ Przycisk zmniejszania głośności
+ Przycisk aparatu
+ Żaden
+
+
+
+ Oparta o litery
+ Oparta o słowa
+
+
+
+
+ Kulka
+ \@
+ Lewy Alt
+ Prawy Alt
+ Vol-Up
+ Vol-Dn
+ Aparat
+ Żaden
+
+
+
+
+ Kulka
+ \@
+ Lewy Alt
+ Prawy
+ Vol-Up
+ Vol-Dn
+ Aparat
+ Żaden
+
+
diff --git a/term/src/main/res/values-pl/strings.xml b/term/src/main/res/values-pl/strings.xml
new file mode 100644
index 000000000..ca8ee3cbe
--- /dev/null
+++ b/term/src/main/res/values-pl/strings.xml
@@ -0,0 +1,152 @@
+
+
+
+ Terminal Emulator
+ Ustawienia
+ Nowe okno
+ Zamknij okno
+ Okna
+ Poprzednie okno
+ Następne okno
+ Wyczyść terminal
+ Wyślij e-mail
+ Przyciski specjalne
+ Pokaż klawiaturę
+
+ Stan tego okna terminala został zresetowany
+
+ Weź WakeLock
+ Zwolnij WakeLock
+ Weź WifiLock
+ Zwolnij WifiLock
+
+ Edytuj tekst
+ Wybierz tekst
+ Kopiuj wszystko
+ Wklej
+ Wyślij przycisk control
+ Wyślij przycisk fn
+
+ Okno %1$d
+
+ Sesja Terminala jest włączona
+
+ Sesja Terminala została zakończona
+
+
+ Ekran
+
+ Pasek stanu
+ Pokaż/ukryj pasek stanu.
+ Pasek stanu
+
+ Pasek akcji
+ Wybierz zachowanie paska akcji (Android 3.0 i nowsze).
+ Zachowanie paska akcji
+
+ Styl kursora
+ Wybierz styl kursora.
+ Styl kursora
+
+ Miganie kursora
+ Wybierz miganie kursora.
+ Miganie kursora
+
+ Tekst
+
+ Domyślnie do UTF-8
+ Czy tryb UTF-8 jest włączony domyślnie.
+
+ Rozmiar czcionki
+ Wybierz wielkość liter.
+ Rozmiar czcionki
+
+ Kolory
+ Wybierz kolor tekstu.
+ Kolor tekstu
+
+ Klawiatura
+
+ Zachowanie przycisku wstecz
+ Wybierz co ma robić wciśniecie przycisku wstecz.
+ Zachowanie przycisku wstecz
+
+ Przycisk control
+ Wybierz przycisk control.
+ Przycisk control
+
+ Przycisk Fn
+ Wybierz przycisk Fn.
+ Przycisk Fn
+
+ Metoda wprowadzania
+ Wybierz metodę wprowadzania.
+ Metoda wprowadzania
+
+ Powłoka
+ Linia poleceń
+ Określ linię powłokę.
+ Powłoka
+
+ Komenda początkowa
+ Wysyłana do powłoki podczas startu.
+ Komenda początkowa
+
+ Typ Terminala
+ Jaki typ terminala zgłosić do shella.
+ Typ Terminala
+
+ Zamknij okno przy wyjściu
+ Czy okno powinno się zamknąć gdy jego shell się zamyka.
+
+ Weryfikuj ścieżki PATH
+ Czy niedostępne katalogi powinny być usunięte z PATH.
+
+ Zezwól na rozszerzanie PATH
+ Czy inne aplikacje powinny mieć możliwość dostarczania dodatkowych komend (dodawanie do PATH).
+
+ Zazwól na dodawanie na początku PATH
+ Czy inne aplikacje powinny mieć możliwość nadpisywania istniejących komend (dodawanie na początku PATH).
+
+ Przyciski Control i Fn
+
+ CTRLKEY Spacja : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Brak ustawionego przycisku Control.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Góra\nFNKEY A : Lewo\nFNKEY S : Dół\nFNKEY D : Prawo\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ Brak ustawionego przycisku Fn.
+
+ Zamknąć to okno?
+
+ Uruchamiaj komendy w Terminal Emulator
+ Pozwalaj na otwieranie okien w Terminal Emulator for Android i wykonywanie komend w tych oknach z wszystkimi prawami Terminal Emulator for Android, wliczając dostęp do internetu i karty SD.
+ Dodawanie komend w Terminal Emulator
+ Pozwól aplikacjom na dodawanie dodatkowych komend (odawanie katalogów do PATH) w Terminal Emulator for Android.
+ Nadpisuj komendy Terminal Emulator
+ Pozwól aplikacjom na nadpisywanie istniejących komend ich własnymi wersjami (dodawanie katalogów na początku PATH) w Terminal Emulator for Android.
+
+ Transkrypt z Terminal Emulator for Android
+ Wyślij transkrypt używając:
+ Nie można wybrać aktywności e-mail żeby wysłać transkrypt.
+
+ Przycisk Alt wysyła ESC
+ Przycisk Alt wysyła ESC.
+ Przycisk Alt nie wysyła ESC.
+
+
diff --git a/term/src/main/res/values-pt-rPT/arrays.xml b/term/src/main/res/values-pt-rPT/arrays.xml
new file mode 100644
index 000000000..b23585567
--- /dev/null
+++ b/term/src/main/res/values-pt-rPT/arrays.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+ Mostrar barra de notificações
+ Esconder barra de notificações
+
+
+
+
+ Mostrar barra de ação
+ Ocultar barra de ação (tocar no topo de ecrã ou na tecla Menu para mostrar)
+
+
+
+ Cursor fixo
+ Cursor a piscar
+
+
+
+ Rectangular
+ Sublinhado
+ Barra vertical
+
+
+
+ 4 x 8 píxeis
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Texto preto em fundo branco
+ Texto branco em fundo preto
+ Texto branco em fundo azul
+ Texto verde em fundo preto
+ Texto âmbar em fundo preto
+ Texto vermelho em fundo preto
+ Texto azul em fundo preto
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Fechar todas as janelas
+ Fechar esta janela
+ Fechar atividades e manter sessão
+ Envia ESC ao terminal
+ Envia TAB ao terminal
+
+
+
+ Trackball
+ Tecla \@
+ Tecla Alt esquerda
+ Tecla Alt direita
+ Aum. volume
+ Dim. volume
+ Tecla da câmara
+ Nenhuma
+
+
+
+ Trackball
+ Tecla \@
+ Tecla Alt esquerda
+ Tecla Alt direita
+ Aum. volume
+ Dim. volume
+ Tecla da câmara
+ Nenhuma
+
+
+
+ Baseado em caracteres
+ Baseado em palavras
+
+
+
+
+ Ball
+ \@
+ Alt esq.
+ Alt dir.
+ Aum. vol.
+ Dim vol.
+ Câmara
+ Nenhuma
+
+
+
+
+ Ball
+ \@
+ Alt esq.
+ Alt dir.
+ Aum. vol.
+ Dim. vol.
+ Câmara
+ Nenhuma
+
+
+
diff --git a/term/src/main/res/values-pt-rPT/strings.xml b/term/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 000000000..aa4ba544d
--- /dev/null
+++ b/term/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,74 @@
+
+
+
+ Terminal Emulator
+ Preferências
+ Reset terminal
+ Email para
+ Teclas especiais
+ Abrir teclado
+
+ Editar texto
+ Seleccionar texto
+ Copiar tudo
+ Colar
+
+
+ Ecrã
+
+ Barra de notificações
+ Mostrar/esconder barra de notificações
+ Barra de notificações
+
+ Estilo do cursor
+ Escolha o estilo do cursor
+ Estilo do cursor
+
+ Piscar do cursor
+ Escolher piscar do cursor
+ Piscar do cursor
+
+ Texto
+
+ Tamanho do texto
+ Escolher a altura dos caracteres em pontos
+ Tamanho do texto
+
+ Côres
+ Escolher côr do texto
+ Côr do texto
+
+ Teclado
+
+ Tecla control
+ Escolher tecla control
+ Tecla control
+
+ Método de entrada
+ Escolher método de entrada
+ Método de entrada
+
+ Shell
+ Linha de comandos
+ Especificar a linha de comando do shell
+ Shell
+
+ Comando inicial
+ Enviar para o shell no arranque
+ Comando inicial
+
+
diff --git a/term/src/main/res/values-pt/arrays.xml b/term/src/main/res/values-pt/arrays.xml
new file mode 100644
index 000000000..a602ca710
--- /dev/null
+++ b/term/src/main/res/values-pt/arrays.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+ Mostrar barra de estado
+ Ocultar barra de estado
+
+
+
+
+ Mostrar barra de ação
+ Ocultar barra de ação (tocar no topo de ecrã ou na tecla Menu para mostrar)
+
+
+
+ Cursor não intermitente
+ Cursor intermitente
+
+
+
+ Retângulo
+ Sublinhado
+ Barra vertical
+
+
+
+ 4 x 8 pixeis
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Texto preto em branco
+ Texto branco em preto
+ Texto branco em azul
+ Texto verde em preto
+ Texto âmbar em preto
+ Texto vermelho em preto
+ Texto azul em preto
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Fechar todas as janelas
+ Fechar esta janela
+ Fechar atividades e manter sessão
+ Envia ESC ao terminal
+ Envia TAB ao terminal
+
+
+
+ Trackball
+ Tecla \@
+ Tecla Alt esquerda
+ Tecla Alt direita
+ Aum. volume
+ Dim. volume
+ Tecla da câmara
+ Nenhuma
+
+
+
+ Trackball
+ Tecla \@
+ Tecla Alt esquerda
+ Tecla Alt direita
+ Aum. volume
+ Dim. volume
+ Tecla da câmara
+ Nenhuma
+
+
+
+ Caracteres
+ Palavra
+
+
+
+
+ Ball
+ \@
+ Alt esq.
+ Alt dir.
+ Aum. vol.
+ Dim vol.
+ Câmara
+ Nenhuma
+
+
+
+
+ Ball
+ \@
+ Alt esq.
+ Alt dir.
+ Aum. vol.
+ Dim. vol.
+ Câmara
+ Nenhuma
+
+
+
diff --git a/term/src/main/res/values-pt/strings.xml b/term/src/main/res/values-pt/strings.xml
new file mode 100644
index 000000000..2d992f983
--- /dev/null
+++ b/term/src/main/res/values-pt/strings.xml
@@ -0,0 +1,143 @@
+
+
+
+ Emulador de Terminal
+ Preferências
+ Nova janela
+ Fechar janela
+ Janelas
+ Anterior
+ Seguinte
+ Repor terminal
+ Enviar mensagem para
+ Teclas especiais
+ Mostrar/ocultar teclado
+
+ O estado desta janela foi restaurado.
+
+ Obter WakeLock
+ Largar WakeLock
+ Obter WifiLock
+ Largar WifiLock
+
+ Editar texto
+ Selecionar texto
+ Copiar tudo
+ Colar
+ Enviar tecla control
+ Enviar tecla fn
+
+ Janela %1$d
+
+ A sessão do Terminal está em execução
+
+ A sessão do Terminal terminou
+
+
+ Ecrã
+
+ Barra de estado
+ Mostrar/ocultar barra de estado.
+ Barra de estado
+
+ Barra de ação
+ Escolha o comportamento da barra de ação (Android 3.0 ou superior).
+ Comportamento da barra de ação
+
+ Estilo do cursor
+ Escolha o estilo do cursor.
+ Estilo do cursor
+
+ Intermitência
+ Escolha a intermitência do cursor.
+ Intermitência
+
+ Texto
+
+ UTF-8 por definição
+ Se o modo UTF-8 está ativo por definição.
+
+ Tamanho da letra
+ Escolha a altura das letras (em pontos).
+ Tamanho da letra
+
+ Cores
+ Escolha a cor do texto.
+ Cor do texto
+
+ Teclado
+
+ Comportamento do botão Voltar
+ Escolha a ação do botão Voltar.
+ Comportamento do botão Voltar
+
+ Tecla Control
+ Escolha a tecla control.
+ Tecla Control
+
+ Tecla Fn
+ Escolha a tecla Fn.
+ Tecla Fn
+
+ Método de inserção
+ Escolha o método de inserção do teclado.
+ Método de inserção
+
+ Consola
+ Linha de comandos
+ Indique a linha de comandos da consola.
+ Consola
+
+ Comando inicial
+ O comando inicial da consola.
+ Comando inicial
+
+ Tipo de Terminal
+ O tipo de terminal a reportar à consola.
+ Tipo de Terminal
+
+ Fechar janela ao sair
+ Se a janela deve ser fechada ao fechar a consola.
+
+ Verificar as entradas PATH
+ Se os diretórios inacessíveis devem ser removidos do PATH.
+
+ Permitir extensões PATH
+ Se outras aplicações podem enviar comandos adicionais (adicionar a PATH).
+
+ Permitir preceder PATH
+ Se outras aplicações podem substituir os comandos existentes (anteceder a PATH).
+
+ Teclas Control e Fn
+
+ CTRLKEY espaço : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Nenhuma tecla definida.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Cima\nFNKEY A : Esquerda\nFNKEY S : Baixo\nFNKEY D : Direita\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ Nenhuma tecla definida.
+
+ Fechar janela?
+
+ Executar comandos aleatórios no emulador de terminal
+ Permite que a aplicação abra janelas no emulador de terminal e executar comandos nessas janelas com todas as permissões, incluido o acesso à Internet e aos cartão SD.
+ Adicionar comandos ao emulador de terminal
+ Permite a disponibilização de comandos adicionais (adicionar diretórios em PATH) no emulador de terminal Android.
+ Substituir comandos do emulador de terminal
+ Permite que a aplicação substitua os comandos existentes pelos seus (anteceder diretórios em PATH) no emulador de terminal Android.
+
diff --git a/term/src/main/res/values-ro/arrays.xml b/term/src/main/res/values-ro/arrays.xml
new file mode 100644
index 000000000..c1233cc4d
--- /dev/null
+++ b/term/src/main/res/values-ro/arrays.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+ Arătaţi bara de stare
+ Ascundeţi bara de stare
+
+
+
+
+ Arătaţi mereu bara de acţiune
+ Ascundeţi bara de acţiune (atingeţi partea de sus a ecranului sau tasta de Meniu pentru a arăta)
+
+
+
+ Cursorul nu clipeşte
+ Cursorul clipeşte
+
+
+
+ Dreptunghi
+ Subliniat
+ Bară verticală
+
+
+
+ 4 x 8 pixeli
+ 6 puncte
+ 7 puncte
+ 8 puncte
+ 9 puncte
+ 10 puncte
+ 12 puncte
+ 14 puncte
+ 16 puncte
+ 20 puncte
+ 24 puncte
+ 28 puncte
+ 32 puncte
+ 36 puncte
+ 42 puncte
+ 48 puncte
+ 64 puncte
+ 72 puncte
+ 96 puncte
+ 144 puncte
+ 288 puncte
+
+
+
+ Text negru pe alb
+ Text alb pe negru
+ Text alb pe albastru
+ Text verde pe negru
+ Text galben pe negru
+ Text roşu pe negru
+ Text holo albastru pe negru
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Închide toate ferestrele
+ Se închide această fereastră numai
+ Se închide activitatea, lăsând sesiunea să funcţioneze
+ Trimite ESC spre terminal
+ Trimite TAB spre terminal
+
+
+
+ Balon Jog
+ Tasta \@
+ Tasta Alt stângă
+ Tasta Alt dreaptă
+ Tasta Volum mărire
+ Tasta Volum micşorare
+ Tasta Cameră foto
+ Niciuna
+
+
+
+ Balon Jog
+ Tasta \@
+ Tasta Alt stângă
+ Tasta Alt dreaptă
+ Tasta Volum mărire
+ Tasta Volum micşorare
+ Tasta Cameră foto
+ Niciuna
+
+
+
+ Bazat pe caracter
+ Bazat pe cuvânt
+
+
+
+ Balon
+ \@
+ Alt stânga
+ Alt dreapta
+ Volum mărire
+ Volum micşorare
+ Cameră foto
+ Niciunul
+
+
+
+ Balon
+ \@
+ Alt stânga
+ Alt dreapta
+ Volum mărire
+ Volum micşorare
+ Cameră foto
+ Niciunul
+
+
diff --git a/term/src/main/res/values-ro/strings.xml b/term/src/main/res/values-ro/strings.xml
new file mode 100644
index 000000000..3c7ea069f
--- /dev/null
+++ b/term/src/main/res/values-ro/strings.xml
@@ -0,0 +1,126 @@
+
+ "Emulator Terminal"
+ "Preferinţe"
+ "Fereastră nouă"
+ "Închideţi fereastra"
+ "Ferestre"
+ "Fereastra anterioră"
+ "Fereastra următoare"
+ "Resetaţi "
+ "E-mail"
+ "Taste speciale"
+ "Comutați tastatura"
+
+ "Această fereastră a fost resetată."
+
+ "Editare text"
+ "Selectaţi textul"
+ "Copiați tot"
+ "Lipeşte"
+ "Trimiteţi tasta Control"
+ "Trimiteţi tasta Fn"
+
+ "Fereastră"
+
+ "Sesiune de terminal se execută"
+
+ "Sesiune de terminal finisată"
+
+ "Ecran"
+
+ "Bara de stare"
+ "Arătaţi/ascundeţi bara de stare."
+ "Bara de stare"
+
+ "Bara de acţiuni"
+ "Alegeți comportamentul barei de acțiuni (Android 3.0 și mai sus)."
+ "Comportament bară de acţiune"
+
+ "Stil cursor"
+ "Alegeţi stilul cursorului."
+ "Stil cursor"
+
+ "Clipirea cursorului"
+ "Alegeţi clipirea cursorului."
+ "Clipire cursor"
+
+ "Text"
+
+ "Implicit UTF-8"
+ "Dacă modul UTF-8 este activat în mod implicit."
+
+ "Dimensiunea fontului"
+ "Alegeţi înălțimea caracterelor în puncte."
+ "Dimensiunea fontului"
+
+ "Culori"
+ "Alegeţi culoarea textului."
+ "Culoare text"
+
+ "Tastatură"
+
+ "Comportamentul butonulul Înapoi"
+ "Alegeţi ce execută apăsarea butonului Înapoi."
+ "Comportamentul butonului Înapoi"
+
+ "Tasta control"
+ "Alegeţi tasta control."
+ "Tasta control"
+
+ "Tasta Fn"
+ "Alegeţi tasta Fn."
+ "Tasta Fn"
+
+ "Metoda de intrare"
+ "Alegeți metoda de intrare pentru tastatură."
+ "Metoda de intrare"
+
+ "Linie de comandă"
+
+ "Comandă inițială"
+ "Comandă inițială"
+
+ "Tip terminal"
+ "Tip terminal"
+
+ "Închideți fereastra la ieșire"
+
+ "Verificați intrările PATH"
+ "Dacă directoare inaccesibile ar trebui să fie eliminate din PATH."
+
+ "Permiteți extensii PATH"
+ "Dacă altor aplicații ar trebui să li se permită să furnizeze comenzi suplimentare."
+
+ "Permiteți prefix PATH"
+ "Dacă altor aplicații ar trebui să li se permită să suprascrie comenzile existente."
+
+ "Tastele control şi funcționale"
+ "Nici o tastă control setată."
+ "Nici o tastă funcţie setată."
+
+ "închideţi această fereastră?"
+
+ "Executare comenzi arbitrare în Emulator Terminal"
+ "Adaugaţi comenzi în Emulatorul Terminal"
+ "Permiteţi aplicației să furnizeze comenzi suplimentare pentru Emulatorul Terminal Android."
+ "Suprascriere comenzi în Emulatorul Terminal"
+ "Permiteţi aplicației să suprascrie comenzile existente, cu versiunile sale proprii în Emulatorul Terminal Android."
+
+ "Tasta Alt trimite ESC"
+ "Tasta Alt trimite ESC."
+ "Tasta Alt nu trimite ESC."
+
\ No newline at end of file
diff --git a/term/src/main/res/values-ru/arrays.xml b/term/src/main/res/values-ru/arrays.xml
new file mode 100644
index 000000000..d336f23da
--- /dev/null
+++ b/term/src/main/res/values-ru/arrays.xml
@@ -0,0 +1,111 @@
+
+
+
+ Трекбол
+ \@
+ LAlt
+ RAlt
+ Vol+
+ Vol-
+ Камера
+ Нет
+
+
+ Всегда показывать
+ Скрывать панель действий (коснитесь верха экрана или клавиши «Меню», чтобы её показать)
+
+
+ Закрыть все окна терминала
+ Закрыть текущее окно терминала
+ Закрывает приложение, но оставляет запущенным терминал
+ Посылает ESC в терминал
+ Посылает TAB в терминал
+
+
+ Чёрный на белом
+ Белый на чёрном
+ Белый на синем
+ Зелёный на чёрном
+ Жёлтый на чёрном
+ Красный на чёрном
+ Голубой на чёрном
+ Соляризованный светлый
+ Соляризованный тёмный
+ Консоль Linux
+
+
+ Трекбол
+ Клавиша @
+ Левый Alt
+ Правый Alt
+ Громкость вверх
+ Громкость вниз
+ Клавиша камеры
+ Нет
+
+
+ Немигающий курсор
+ Мигающий курсор
+
+
+ Прямоугольник
+ Подчеркивание
+ Вертикальная черта
+
+
+ Трекбол
+ Клавиша @
+ Левый Alt
+ Правый Alt
+ Громкость вверх
+ Громкость вниз
+ Клавиша камеры
+ Нет
+
+
+ 4 x 8 пикселей
+ 6 пунктов
+ 7 пунктов
+ 8 пунктов
+ 9 пунктов
+ 10 пунктов
+ 12 пунктов
+ 14 пунктов
+ 16 пунктов
+ 20 пунктов
+ 24 пункта
+ 28 пунктов
+ 32 пункта
+ 36 пунктов
+ 42 пункта
+ 48 пунктов
+ 64 пункта
+ 72 пункта
+ 96 пунктов
+ 144 пункта
+ 288 пунктов
+
+
+ По символам
+ По словам
+
+
+ Автоматически
+ Альбомная
+ Портретная
+
+
+ Показывать
+ Скрывать
+
+
+ Трекбол
+ \@
+ LAlt
+ RAlt
+ Vol+
+ Vol-
+ Камера
+ Нет
+
+
diff --git a/term/src/main/res/values-ru/strings.xml b/term/src/main/res/values-ru/strings.xml
new file mode 100644
index 000000000..ba2275796
--- /dev/null
+++ b/term/src/main/res/values-ru/strings.xml
@@ -0,0 +1,151 @@
+
+
+ Ярлык терминала
+ Терминал здесь
+ Аргументы
+ Поиск команды
+ Текст значок
+ command
+ Командное окно требует полный путь без аргументов. Для других команд использовать окно Аргументы (пример: CD/SD-карта).
+ --пример=\"a\"
+ СОЗДАТЬ ЗНАЧОК ТЕКСТА
+ ВЫБЕРИТЕ ЦЕЛЬ ЯРЛЫКА
+ Метка ярлыка
+ Ярлык терминала
+ Клавиша Alt посылает ESC
+ Клавиша Alt не посылает ESC
+ Клавиша Alt посылает ESC
+ Терминал
+ Закрыть окно
+ Введите текст для значка
+ LOCK
+ Закрыть это окно?
+ Управляющие клавиши отключены
+ "CTRLKEY + Пробел: Control-@ (NUL)
+ CTRLKEY + A..Z: Control-A..Z
+ CTRLKEY + 5: Control-]
+ CTRLKEY + 6: Control-^
+ CTRLKEY + 7: Control-_
+ CTRLKEY + 9: F11
+ CTRLKEY + 0: F12"
+ Функциональные клавиши отключены
+ "FNKEY 1..9 : F1-F9
+ FNKEY + 0: F10
+ FNKEY + W: Вверх
+ FNKEY + A: Влево
+ FNKEY + S: Вниз
+ FNKEY + D: Вправо
+ FNKEY + P: PageUp
+ FNKEY + N: PageDown
+ FNKEY + T: Tab
+ FNKEY + L: | (вертикальная черта)
+ FNKEY + U: _ (подчёркивание)
+ FNKEY + E: Control-[ (ESC)
+ FNKEY + X: Delete
+ FNKEY + I: Insert
+ FNKEY + H: Home
+ FNKEY + F: End
+ FNKEY + .: Control-\\
+"
+ Управляющие и функциональные клавиши
+ Копировать всё
+ Поведение панели действий
+ Поведение кнопки «Назад»
+ Цвет текста
+ Клавиша Control
+ Мигание курсора
+ Вид курсора
+ Клавиша Fn
+ Размер шрифта
+ Способ ввода
+ Команды запуска
+ Поворот экрана
+ Оболочка
+ Строка состояния
+ Тип терминала
+ Выключить WakeLock
+ Выключить WifiLock
+ Изменить
+ Отправить текст из терминала с помощью:
+ Не найдено приложения для отправки текста
+ Вывод терминала Android
+ Включить WakeLock
+ Включить WifiLock
+ Изменить тему
+ Внешние хранилища недоступно
+ Или введите путь здесь
+ ВЫБОР ФАЙЛОВ
+ Помощь
+ Клавиатура
+ Новое окно
+ Следующее окно
+ Вставить
+ Добавление команд в терминала
+ Переопределение команд в терминале
+ Выполнение произвольных команд в терминале
+ Приложение сможет обеспечить дополнительные команды (добавить каталоги в PATH) для терминала.
+ Позволяет приложению переопределить существующие команды своими (перезаписывать PATH) в терминале.
+ Позволяет приложению открытие новых окон в терминале Android и запуск команд в этих окнах со всеми разрешениями терминала, в том числе доступ к Интернету и SD-карте.
+ Настройки
+ Предыдущее окно
+ Терминальный сеанс завершён
+ Перезапустить терминал
+ Терминал перезапущен
+ Экран
+ Выбрать текст
+ Передать кнопку Control
+ Отправить по эл.почте
+ Передать кнопку Fn
+ Запущен терминальный сеанс
+ Командная оболочка
+ Сочетания клавиш
+ Настройка поведения панели действий (Android 3.0 и выше)
+ Разрешить другим приложениям изменять существующие команды (добавление к началу PATH)
+ Выбор действия при нажатии кнопки «Назад»
+ Окно закроется, если выйти из оболочки
+ Выбор цветовой гаммы
+ Выбор аналога клавиши Control
+ Выберите параметры мигания курсора
+ Выберите стиль курсора
+ Разрешить другим приложениям обеспечивать дополнительные команды (добавление к PATH)
+ Выбор аналога клавиши Fn
+ Выбор размера шрифта
+ Установка пути к папке, которая будет использована в качестве домашней (должны быть права записи)
+ Выберите способ ввода для экранной клавиатуры
+ Передаются облочке при запуске терминала
+ Определяет, будут ли события касания и прокрутки передаваться в терминал как Escape-последовательности
+ Выбор ориентации экрана
+ Укажите строку обращения к командной оболочке
+ Показывать или скрывать строку состояния
+ Выбор типа терминала для использования в оболочке
+ Применить режим UTF-8 по умолчанию
+ Удалять недоступные каталоги из PATH
+ Текст
+ Панель действий
+ Разрешить перезапись PATH
+ Поведение кнопки «Назад»
+ Закрытие окна при выходе
+ Цветовая схема
+ Клавиша Control
+ Мигание курсора
+ Вид курсора
+ Включить расширения PATH
+ Клавиша Fn
+ Размер шрифта
+ Домашняя папка
+ Способ ввода
+ Команды запуска
+ Передавать события мыши
+ Поворот экрана
+ Обработчик команд
+ Строка состояния
+ Тип терминала
+ Использовать сочетания клавиш
+ По умолчанию UTF-8
+ Проверять пути в PATH
+ Экранная клавиатура
+ Сочетания клавиш отключены
+ Ctrl-Tab: переключение окон, Ctrl-Shift-N: новое окно, Ctrl-Shift-V: вставить
+ Окна
+ Окно %1$d
+
diff --git a/term/src/main/res/values-sk/arrays.xml b/term/src/main/res/values-sk/arrays.xml
new file mode 100644
index 000000000..ecf9dcafe
--- /dev/null
+++ b/term/src/main/res/values-sk/arrays.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+ Zobraziť stavový riadok
+ Skryť stavový riadok
+
+
+
+ Vždy zobraziť panel akcií
+ Skryť panel akcií (pre zobrazenie sa dotknite vrchu obrazovky alebo tlačidla Ponuka)
+
+
+
+ Neblikajúci kurzor
+ Blikajúci kurzor
+
+
+
+ Automatická
+ Na šírku
+ Na výšku
+
+
+
+ Obdĺžnikový
+ Podčiarknutý
+ Zvislá čiara
+
+
+
+ 4 x 8 pixelov
+ 6 bodov
+ 7 bodov
+ 8 bodov
+ 9 bodov
+ 10 bodov
+ 12 bodov
+ 14 bodov
+ 16 bodov
+ 20 bodov
+ 24 bodov
+ 28 bodov
+ 32 bodov
+ 36 bodov
+ 42 bodov
+ 48 bodov
+ 64 bodov
+ 72 bodov
+ 96 bodov
+ 144 bodov
+ 288 bodov
+
+
+
+ Čierny text na bbielom
+ Biely text na čiernom
+ Biely text na modrom
+ Zelený text na čiernom
+ Jantárový text na čiernom
+ Červený text na čiernom
+ Holo modrý text na čiernom
+ Solarizovaná svetlá
+ Solarizovaná tmavá
+ Linuxová konzola
+
+
+
+ Zavrieť všetky okná terminálu
+ Zavrieť iba aktuálne okno terminálu
+ Zavrieť aktivitu, ponechať reláciu bežiacou
+ Odoslať ESC do terminálu
+ Odoslať TAB do terminálu
+
+
+
+ Gulička
+ Kláves \@
+ Kláves Ľavý Alt
+ Kláves Pravý Alt
+ Kláves Hlasitosť+
+ Kláves Hlasitosť-
+ Kláves Fotoaparát
+ Žiadny
+
+
+
+ Gulička
+ Kláves \@
+ Kláves Ľavý Alt
+ Kláves Pravý Alt
+ Kláves Hlasitosť+
+ Kláves Hlasitosť-
+ Kláves Fotoaparát
+ Žiadny
+
+
+
+ Po znakoch
+ Po slovách
+
+
+
+
+ Gulička
+ \@
+ Ľavý Alt
+ Pravý Alt
+ Hlasitosť+
+ Hlasitosť-
+ Fotoaparát
+ Žiadny
+
+
+
+
+ Gulička
+ \@
+ Ľavý Alt
+ Pravý Alt
+ Hlasitosť+
+ Hlasitosť-
+ Fotoaparát
+ Žiadny
+
+
diff --git a/term/src/main/res/values-sk/strings.xml b/term/src/main/res/values-sk/strings.xml
new file mode 100644
index 000000000..b1138458c
--- /dev/null
+++ b/term/src/main/res/values-sk/strings.xml
@@ -0,0 +1,188 @@
+
+
+
+ Emulátor terminálu
+ Nastavenia
+ Nové okno
+ Zavrieť okno
+ Okná
+ Dalšie okno
+ Predchádzajúce okno
+ Obnoviť terminál
+ Poslať e-mailom
+ Špeciálne klávesy
+ Prepnúť softvérovú klávesnicu
+
+ Stav terminálu pre toto okno bol obnovený.
+
+ Zabrániť uspaniu
+ Povoliť uspanie
+ Zabrániť odpojeniu Wi-Fi
+ Povoliť odpojenie Wi-Fi
+
+ Upraviť text
+ Vybrať text
+ Kopírovať všetko
+ Prilepiť
+ Poslať ovládací kláves
+ Poslať funkčný kláves
+
+ Okno %1$d
+
+ Terminálová relácia je spustená
+
+ Terminálová relácia bola ukončená
+
+
+ Obrazovka
+
+ Stavový riadok
+ Zobraziť/skryť stavový riadok.
+ Stavový riadok
+
+ Panel akcií
+ Vybrať správanie panela akcií (Android 3.0 a vyšší).
+ Správanie panela akcií
+
+ Orientácia obrazovky
+ Vybrať správanie orientácie obrazovky.
+ Správanie orientácie obrazovky
+
+ Štýl kurzoru
+ Vybrať štýl kurzoru.
+ Štýl kurzoru
+
+ Blikanie kurzoru
+ Vybrať blikanie kurzoru.
+ Blikanie kurzoru
+
+ Text
+
+ Predvolene do UTF-8
+ Režim UTF-8 je predvolene povolený.
+
+ Veľkosť písma
+ Vybrať výšku znaku v bodoch.
+ Veľkosť fontu
+
+ Farby
+ Vybrať farbu textu.
+ Farba textu
+
+ Klávesnica
+
+ Správanie tlačidla Späť
+ Vybrať správanie tlačidla Späť.
+ Správanie tlačidla Späť
+
+ Ovládací kláves
+ Vybrať ovládací kláves.
+ Ovládací kláves
+
+ Funkčný kláves
+ Vybrať funkčný kláves.
+ Funkčný kláves
+
+ Metóda vstupu
+ Vybrať metódu zadávania pre softvérovú klávesnicu.
+ Metóda vstupu
+
+ Konzola
+ Príkazový riadok
+ Vybrať príkaz pre konzolu.
+ Konzola
+
+ Počiatočný príkaz
+ Poslať do konzoly po spustení.
+ Počiatočný príkaz
+
+ Typ terminálu
+ Typ terminálu pre nahlásenie do konzoly.
+ Typ terminálu
+
+ Zavrieť okno po ukončení
+ Po ukončení konzoly zavrieť okno.
+
+ Overiť položky v PATH
+ Odstrániť neprístupné priečinky z PATH.
+
+ Povoliť rozšírenia PATH
+ Umožniť ostatným aplikáciám poskytnúť ďalšie príkazy (pridať do PATH).
+
+ Povoliť podradenie PATH
+ Umožniť ostatným aplikáciám prepísať existujúce príkazy (pridať na začiatok PATH).
+
+ Domovský priečinok
+ Umiestnenie zapisovateľného priečinka, ktorý bude použitý ako domovský.
+
+ Ovládacie a funkčné klávesy
+ CTRLKEY medzera : Ctrl-@ (NUL)\nCTRLKEY A..Z : Ctrl-A..Z\nCTRLKEY 5 : Ctrl-]\nCTRLKEY 6 : Ctrl-^\nCTRLKEY 7 : Ctrl-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Ovládacie klávesy nie sú nastavené.
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : hore\nFNKEY A : vľavo\nFNKEY S : dole\nFNKEY D : vpravo\nFNKEY P : stránka hore\nFNKEY N : stránka dole\nFNKEY T : tabulátor\nFNKEY L : | (zvislá čiara)\nFNKEY U : _ (podčiarkovník)\nFNKEY E : Ctrl-[ (ESC)\nFNKEY X : vymazať\nFNKEY I : vložiť\nFNKEY H : domov\nFNKEY F : koniec\nFNKEY . : Ctrl-\\\n
+ Funkčné klávesy nie sú nastavené.
+
+ Zavrieť toto okno?
+
+ Spustiť ľubovoľné príkazy
+ Umožniť aplikáciám otvoriť nové okno v Emulátori terminálu pre Android a spustiť príkazy v tomto okne so všetkými povoleniami terminálu a to vrátane prístupu na Internet a ku karte SD.
+ Pridať príkazy do Emulátora terminálu Android
+ Umožňuje aplikáciám poskytnúť ďalšie príkazy (pridať priečinky do PATH) v Emulátori terminálu pre Android.
+ Prepísať príkazy v Emulátori terminálu Android
+ Umožňuje aplikáciám prepísať existujúce príkazy so svojimi vlastnými verziami (podradiť priečinky v PATH) v Emulátori terminálu pre Android.
+
+ Prepis z Emulátora terminálu pre Android
+ Poslať prepis prostredníctvom
+ Nie je možné vybrať e-mailovú aktivitu na poslanie prepisu.
+
+ Posielať ESC pomocou klávesu Alt
+ Kláves Alt posiela ESC.
+ Kláves Alt neposiela ESC.
+
+ Posielať udalosti myši
+ Posielať udalosti kliknutia a posunu ako sekvencie ESCAPE do terminálu.
+
+ Používať klávesové skratky
+ Ctrl-Tab: prepnúť okno, Ctrl-Shift-N: nové okno, Ctrl-Shift-V: prilepiť.
+ Klávesové skratky sú zakázané.
+
+ Pomocník
+ Otvoriť terminál
+
+ Skratka terminálu
+ VYBERTE SÚBOR
+ Nedostupný externý ukladací priestor
+ Alebo zadajte umiestnenie sem.
+ Zmeniť tému
+
+ príkaz
+ --príklad=\""a\"
+ Nájsť príkaz
+ VYBERTE CIEĽ SKRATKY
+ Príkazové okno vyžaduje celé umiestnenie, žiadne argumenty. Pre ostatné príkazy použite okno pre argumenty (napríklad: cd /sdcard).
+ Argumenty\:
+ Názov skratky\:
+ Text ikony
+ Vytvoriť text ikony
+ Skratka terminálu
+
+ α
+ Č
+ Z
+ M
+ Zamknúť
+ Zadajte text ikony
+
diff --git a/term/src/main/res/values-sr/arrays.xml b/term/src/main/res/values-sr/arrays.xml
new file mode 100644
index 000000000..e9c29f42a
--- /dev/null
+++ b/term/src/main/res/values-sr/arrays.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+ Прикажи траку стања
+ Сакриј траку стања
+
+
+
+
+ Увек прикажи траку радњи
+ Сакриј траку радњи (за приказ додирните врх екрана или Мени тастер)
+
+
+
+ Аутоматски
+ Пејзаж
+ Портрет
+
+
+
+ Статични курсор
+ Трепћући курсор
+
+
+
+ Правоугаоник
+ Подвлака
+ Вертикална трака
+
+
+
+ 4 x 8 px
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Црни текст на белом
+ Бели текст на црном
+ Бели текст на плавом
+ Зелени текст на црном
+ Јантарни текст на црном
+ Црвени текст на црном
+ Холо плави текст на црном
+ Светла соларизована
+ Тамна соларизована
+ Линукс конзола
+
+
+
+ Затвара све прозоре терминала
+ Затвара само овај прозор терминала
+ Затвара активност, оставља сесије покренуте
+ Шаље ESC терминалу
+ Шаље TAB терминалу
+
+
+
+ Џог куглица
+ \@ тастер
+ Леви Алт тастер
+ Десни Алт тастер
+ Звук Горе тастер
+ Звук Доле тастер
+ Камера тастер
+ Ниједан
+
+
+
+ Џог куглица
+ \@ тастер
+ Леви Алт тастер
+ Десни Алт тастер
+ Звук Горе тастер
+ Звук Доле тастер
+ Камера тастер
+ Ниједан
+
+
+
+ Заснована на знаковима
+ Заснована на речима
+
+
+
+
+ Куглица
+ \@
+ Леви-Алт
+ Десни-Алт
+ Звук-Горе
+ Звук-Доле
+ Камера
+ Ниједан
+
+
+
+
+ Куглица
+ \@
+ Леви-Алт
+ Десни-Алт
+ Звук-Горе
+ Звук-Доле
+ Камера
+ Ниједан
+
+
diff --git a/term/src/main/res/values-sr/strings.xml b/term/src/main/res/values-sr/strings.xml
new file mode 100644
index 000000000..352d9c42a
--- /dev/null
+++ b/term/src/main/res/values-sr/strings.xml
@@ -0,0 +1,195 @@
+
+
+
+ Емулатор терминала
+ Поставке
+ Нови прозор
+ Затвори прозор
+ Прозори
+ Прет. прозор
+ След. прозор
+ Поново постави терм
+ Пошаљи е-поштом
+ Посебни тастери
+ Пребаци софтверску тастатуру
+
+ Стање терминала овог прозора је поново постављено.
+
+ Узми блокаду буђења
+ Отпусти блокаду буђења
+ Узми блокаду бежичног
+ Отпусти блокаду бежичног
+
+ Уреди текст
+ Изабери текст
+ Копирај све
+ Налепи
+ Пошаљи Цтрл унос
+ Пошаљи Фн унос
+
+ Прозор %1$d
+
+ Сесија терминала је у току
+
+ Сесија терминала је завршена
+
+
+ Екран
+
+ Трака стања
+ Прикажи/сакриј траку стања.
+ Трака стања
+
+ Трака радњи
+ Одредите понашање траке радњи (Андроид 3.0 и новији).
+ Понашање траке радњи
+
+ Оријентација екрана
+ Одредите понашање оријентације екрана.
+ Оријентација екрана
+
+ Стил курсора
+ Изаберите стил курсора.
+ Стил курсора
+
+ Трептање курсора
+ Одредите трептање курсора.
+ Трептање курсора
+
+ Текст
+
+ Подразумевај УТФ-8
+ Да ли је УТФ-8 режим подразумевано омогућен.
+
+ Величина фонта
+ Одредите величину фонта у тачкама.
+ Величина фонта
+
+ Боје
+ Изаберите боју текста.
+ Боја текста
+
+ Тастатура
+
+ Понашање дугмета за назад
+ Шта ће да деси притиском на дугме за назад.
+ Понашање дугмета за назад
+
+ Контрол тастер
+ Изаберите контрол (Цтрл) тастер.
+ Контрол тастер
+
+ Фн тастер
+ Изаберите функцијски (Фн) тастер.
+ Фн тастер
+
+ Метода уноса
+ Изаберите методу уноса за софтверску тастатуру.
+ Метода уноса
+
+ Шкољка
+ Командна линија
+ Одредите командну линију шкољке.
+ Шкољка
+
+ Почетна наредба
+ Наредба послата шкољки по покретању.
+ Почетна наредба
+
+ Тип терминала
+ Који тип терминала да буде пријављен шкољки.
+ Тип терминала
+
+ Затвори прозор по излазу
+ Да ли да прозор буде затворен по излазу из његове шкољке.
+
+ Овери PATH уносе
+ Да ли да недоступни директоријуми буду уклољени из PATH.
+
+ Дозволи PATH проширења
+ Да ли осталим апликацијама дозволити додавање додатне наредбе (да додају у PATH).
+
+ Дозволи PATH префиксе
+ Да ли осталим апликацијама дозволити премошћавање постојеће наредбе (да додају на почетку PATH).
+
+ Домаћа фасцикла
+ Путања до уписиве фасцикле која ће бити коришћена као HOME.
+
+ Контролни и функцијски тастери
+
+ CTRLKEY Размак : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ Контролни тастер није постављен.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Горе\nFNKEY A : Лево\nFNKEY S : Доле\nFNKEY D : Десно\nFNKEY P : СтрГоре\nFNKEY N : СтрДоле\nFNKEY T : Таб\nFNKEY L : | (цев)\nFNKEY U : _ (подвлака)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Обриши\nFNKEY I : Уметни\nFNKEY H : Почетак\nFNKEY F : Крај\nFNKEY . : Control-\\\n
+ Функцијски тастер није постављен.
+
+ Затворити овај прозор?
+
+ Покретање произвољних наредби у Емулатору Терминала
+ Омогућава апликацији да отвара нове прозоре у Емулатору Терминала за Андроид и покреће наредбе у тим прозорима са свим дозволама Емулатора Терминала, укључујући приступ интернету и вашој СД картици.
+ Додавање наредби у Емулатор Терминала
+ Дозвољава апликацији да пружи додатне наредбе (додавање директоријума у PATH) за Емулатор Терминала за Андроид.
+ Премошћавање наредби у Емулатору Терминала
+ Дозвољава апликацији да премости постојеће наредбе својим (префиксује директоријуме у PATH) у Емулатору Терминала за Андроид.
+
+ Препис са Емулатора Терминала за Андроид
+ Пошаљи препис користећи:
+ Не могу да изаберем активност е-поште за слање преписа.
+
+ Алт тастер шаље ESC
+ Алт тастер шаље ESC.
+ Алт тастер не шаље ESC.
+
+ Шаљи догађаје миша
+ Слање додира и клизања терминалу као секвенце излаза.
+
+ Користи пречице тастатуре
+ Ctrl-Tab: пребаци прозор, Ctrl-Shift-N: нови прозор, Ctrl-Shift-V: налепи.
+ Пречице тастатуре су онемогућене.
+
+ Помоћ
+ Терминал овде
+
+
+ "Терминалска пречица"
+
+ ИЗБОР ФАЈЛА
+ Спољашње складиште није доступно
+ Или унесите путању овде.
+ Промени тему
+
+ наредба
+ --пример=\""a\"
+ Нађи наредбу
+ ИЗБОР МЕТЕ ПРЕЧИЦЕ
+ Окно наредбе захтева пуну путању, без аргумената. За остале наредбе користите окно аргумената (нпр: cd /sdcard).
+ Аргументи\:
+ Етикета пречице\:
+ Текстуална икона
+ ТЕКСТУАЛНА ИКОНА
+ Пречица терминала
+
+ α
+ Р
+ Г
+ Б
+ ЗАКЉУЧАЈ
+ Унесите текст иконе
+
+
diff --git a/term/src/main/res/values-sv/arrays.xml b/term/src/main/res/values-sv/arrays.xml
new file mode 100644
index 000000000..dfaaa07dc
--- /dev/null
+++ b/term/src/main/res/values-sv/arrays.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+ Visa statusfältet
+ Dölj statusfältet
+
+
+
+
+ Always show action bar
+ Hide action bar (touch top of screen or Menu key to show)
+
+
+
+ Icke-blinkande markör
+ Blinkande markör
+
+
+
+ Rektangel
+ Understruken
+ Vertikalt streck
+
+
+
+ 4x8 Pixlar
+ 6 Punkter
+ 7 Punkter
+ 8 Punkter
+ 9 Punkter
+ 10 Punkter
+ 12 Punkter
+ 14 Punkter
+ 16 Punkter
+ 20 Punkter
+ 24 Punkter
+ 28 Punkter
+ 32 Punkter
+ 36 Punkter
+ 42 Punkter
+ 48 Punkter
+ 64 Punkter
+ 72 Punkter
+ 96 Punkter
+ 144 Punkter
+ 288 Punkter
+
+
+
+ Svart text på vit
+ Vit text på svart
+ Vit text på blå
+ Grön text på svart
+ Orange text på svart
+ Röd text på svart
+ Blå text på svart
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Closes all terminal windows
+ Closes this terminal window only
+ Closes activity, leaving sessions running
+ Sends ESC to terminal
+ Sends TAB to terminal
+
+
+
+ Trackball
+ \@-tangent
+ Vänster Alt-tangent
+ Höger Alt-tangent
+ Volym upp
+ Volym ner
+ Kamera-tangent
+ None
+
+
+
+ Trackball
+ \@-tangent
+ Vänster Alt-tangent
+ Höger Alt-tangent
+ Volym upp
+ Volym ner
+ Kamera-tangent
+ None
+
+
+
+ Teckenbaserade
+ Ordbaserade
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
diff --git a/term/src/main/res/values-sv/strings.xml b/term/src/main/res/values-sv/strings.xml
new file mode 100644
index 000000000..7ad3dff1c
--- /dev/null
+++ b/term/src/main/res/values-sv/strings.xml
@@ -0,0 +1,52 @@
+
+
+ Terminalemulator
+ Inställningar
+ Återställ terminal
+ E-posta till
+ Special tangenter
+ Växla till virtuellt tangentbord
+ Aktivera VäckningsLås
+ Inaktivera VäckningsLås
+ Aktivera WifiLås
+ Inaktivera WifiLås
+ Redigera text
+ Markera text
+ Kopiera allt
+ Klistra in
+ Terminalsessionen är igång
+ Skärm
+ Statusfält
+ Visa/Dölj statusfält
+ Statusfält
+ Markörstil
+ Välj markörstil.
+ Markörstil
+ Blinkande markör
+ Välj blinkande markör
+ Blinkande markör
+ Text
+ Teckenstorlek
+ Välj teckenhöjd i punkter.
+ Teckenstorlek
+ Färger
+ Välj textfärg.
+ Textfärg
+ Tangentbord
+ Ctrl-tangent
+ Välj Ctrl-tangent
+ Ctrl-tangent
+ Fn-tangent
+ Välj Fn-tangent.
+ Fn-tangent
+ Inmatningsmetod
+ Välj inmatningsmetod för virtuellt tangentbord.
+ Inmatningsmetod
+ Skal
+ Kommandorad
+ Ange skalkommandorad.
+ Skal
+ Inledande kommando
+ Skickas till skalet när det börjar.
+ Inledande kommando
+
diff --git a/res/values-it/arrays.xml b/term/src/main/res/values-tr/arrays.xml
similarity index 52%
rename from res/values-it/arrays.xml
rename to term/src/main/res/values-tr/arrays.xml
index 1c0c48cda..9d1a61e24 100644
--- a/res/values-it/arrays.xml
+++ b/term/src/main/res/values-tr/arrays.xml
@@ -17,23 +17,23 @@
- Mostra status bar
- Nascondi status bar
+ Uyarı çubuğunu göster
+ Uyarı çubuğunu sakla
- Cursore non lampeggiante
- Cursore lampeggiante
+ Non-blinking cursor
+ Blinking cursor
- Rettangolo
+ RectangleUnderline
- Barra verticale
+ Vertical bar
- 4 x 8 pixels
+ 4 x 8 piksel6 pt7 pt8 pt
@@ -43,29 +43,44 @@
14 pt16 pt20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
- Testo nero su bianco
- Testo bianco su nero
- Testo bianco su blu
- Testo verde su nero
- Testo ambra su nero
- Testo rosso su nero
+ Beyaz arkaplan üzerinde siyah metin
+ Siyah arkaplan üzerinde beyaz metin
+ Mavi arkaplan üzerinde beyaz metin
+ Siyah arkaplan üzerinde yeşil metin
+ Siyah arkaplan üzerinde turuncu metin
+ Siyah arkaplan üzerinde kırmızı metin
+ Solarized Light
+ Solarized Dark
+ Linux Console
- Trackball
- Tasto \@
- Tasto Alt sin.
- Tasto Alt des.
- Pulsante Vol +
- Pulsante Vol -
+ Jog ball
+ \@ Tuşu
+ Sol Alt Tuşu
+ Sağ Alt Tuşu
+ Ses Arttırma Tuşu
+ Ses Kısma Tuşu
+ Kamera Tuşu
- Character-based
- Word-based
+ Harf tabanlı
+ Kelime tabanlı
diff --git a/term/src/main/res/values-tr/strings.xml b/term/src/main/res/values-tr/strings.xml
new file mode 100644
index 000000000..b45fa127e
--- /dev/null
+++ b/term/src/main/res/values-tr/strings.xml
@@ -0,0 +1,73 @@
+
+
+
+ Terminal Emülatörü
+ Tercihler
+ Terminali yeniden başlat
+ Email olarak yolla
+ Özel tuşlar
+ Ekran klavyesine geç
+
+ Metni düzenle
+ Metni seç
+ Hepsini kopyala
+ Yapıştır
+
+
+ Ekran
+
+ Uyarı çubuğu
+ Uyarı çubuğunu göster/gizle.
+ Uyarı çubuğu
+
+ İmleç stili
+ İmleç stili seçin.
+ İmleç stili
+
+ Cursor blink
+ Choose cursor blink.
+ Cursor blink
+
+ Metin
+
+ Karakter boyutu
+ Karakter büyüklüğü seçin.
+ Karakter boyutu
+
+ Renkler
+ Metin rengini seçin.
+ Metin rengi
+
+ Klavye
+
+ Kontrol tuşu
+ Kontrol tuşunu seçin.
+ Kontrol tuşu
+
+ Giriş yöntemi
+ Ekran klavyesi için giriş yöntemi seçin.
+ Giriş yöntemi
+
+ Kabuk
+ Komut satırı
+ Kabukta kullanılacak komut satırını belirtin.
+ Kabuk
+
+ Başlangıç komutu
+ Başlangıçta kabukta çalıştır.
+ Başlangıç komutu
+
diff --git a/term/src/main/res/values-uk/arrays.xml b/term/src/main/res/values-uk/arrays.xml
new file mode 100644
index 000000000..b9b7cf76d
--- /dev/null
+++ b/term/src/main/res/values-uk/arrays.xml
@@ -0,0 +1,105 @@
+
+
+
+ Показувати
+ Приховувати
+
+
+ Завжди показувати бар дії
+ Сховати дії бару (торкніться верху екрану або клавіші Меню, щоб показати)
+
+
+ Не-миготливий курсор
+ Миготливий курсор
+
+
+ Прямокутник
+ Підкреслення
+ Вертикальний бар
+
+
+ 4 x 8 пікселів
+ 6 пікселів
+ 7 пікселів
+ 8 пікселів
+ 9 пікселів
+ 10 пікселів
+ 12 пікселів
+ 14 пікселів
+ 16 пікселів
+ 20 пікселів
+ 24 пікселів
+ 28 пікселів
+ 32 пікселів
+ 36 пікселів
+ 42 пікселів
+ 48 пікселів
+ 64 пікселів
+ 72 пікселів
+ 96 пікселів
+ 144 пікселів
+ 288 пікселів
+
+
+ Чорний на білому
+ Білий на чорному
+ Білий на синьому
+ Зелений на чорному
+ Жовтий на чорному
+ Червоний на чорному
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+ Закрити всі вікна терміналу
+ Закрити тільки це вікно терміналу
+ Активне закриття, решти запущених сеансів
+ Послати ESC в термінал
+ Послати TAB в термінал
+
+
+ Куля Jog
+ Клавіша @
+ Лівий Alt
+ Правий Alt
+ Гучність вверх
+ Гучність вниз
+ Клавіша камери
+ Нема
+
+
+ Куля Jog
+ Клавіша @
+ Лівий Alt
+ Правий Alt
+ Гучність вверх
+ Гучність вниз
+ Клавіша камери
+ Нема
+
+
+ За знаками
+ За словами
+
+
+ Куля Jog
+ Клавіша @
+ Лівий Alt
+ Правий Alt
+ Гучність вверх
+ Гучність вниз
+ Клавіша камери
+ Нема
+
+
+ Куля Jog
+ Клавіша @
+ Лівий Alt
+ Правий Alt
+ Гучність вверх
+ Гучність вниз
+ Клавіша камери
+ Нема
+
+
diff --git a/term/src/main/res/values-uk/strings.xml b/term/src/main/res/values-uk/strings.xml
new file mode 100644
index 000000000..2f267a97e
--- /dev/null
+++ b/term/src/main/res/values-uk/strings.xml
@@ -0,0 +1,116 @@
+
+
+ Емулятор Терміналу
+ Налаштування
+ Нове вікно
+ Закрити вікно
+ Вікна
+ Попереднє вікно
+ Наступне вікно
+ Скинути термінал
+ Відіслати Email
+ Спеціальні клавіші
+ Екранна клавіатура
+ "Термінальний стан цього вікна було скинуто."
+ Ввімкнути WakeLock
+ Вимкнути WakeLock
+ Ввімкнути WifiLock
+ Вимкнути WifiLock
+ Змінити
+ Вибрати текст
+ Копіювати все
+ Вставити
+ Відіслати кнопкою Control
+ Відіслати кнопкою Fn
+ Вікно %1$d
+ Термінальний сеанс запущений
+ Термінальний сеанс завершений
+ Екран
+ Статус бар
+ Показати/Приховати статус бар.
+ Статус бар
+ Бар дії
+ Виберіть поведінку бару дії (Android 3.0 і вище).
+ Поведінка бару дії
+ Текст
+ За замовчуванням UTF-8
+ Застосувати режим UTF-8 за замовчуванням.
+ Розмір шрифту
+ Виберіть розмір шрифту.
+ Розмір шрифту
+ Кольори
+ Виберіть колір тексту.
+ Колір тексту
+ Клавіатура
+ Поведінка кнопки Назад
+ Вибір дії при натисканні кнопки назад.
+ Поведінка кнопки Назад
+ Клавіша Control
+ Виберіть що буде клавішею Control.
+ Клавіша Control
+ Клавіша Fn
+ Виберіть що буде клавішею Fn.
+ Клавіша Fn
+ Спосіб введення
+ Виберіть спосіб введення для екранної клавіатури.
+ Спосіб введення
+ Командна оболонка
+ Командний рядок
+ Вкажіть рядок звернення до командної оболонки.
+ Оболонка
+ Команди запуску
+ Передаються облонці при запуску терміналу.
+ Команди запуску
+ Тип терміналу
+ Вибір типу терміналу, для використання в оболонці.
+ Тип терміналу
+ Закриття вікна при виході
+ Вікно закриється якщо вийти з оболонки.
+ Перевіряти ШЛЯХ даних
+ Видаляти недоступні директорії з ШЛЯХУ.
+ Допустимі дозволи ШЛЯХУ
+ Дозволити іншим програмам забезпечувати додаткові команди (додавання до ШЛЯХУ).
+ Допускати ШЛЯХ додавання
+ Дозволити іншим програмам анулювати існуючі команди (додавання до початку ШЛЯХУ).
+ Керуючі та Функціональні Клавіші
+ CTRLKEY Space : Control-@ (NUL)
+CTRLKEY A..Z : Control-A..Z
+CTRLKEY 5 : Control-]
+CTRLKEY 6 : Control-^
+CTRLKEY 7 : Control-_
+CTRLKEY 9 : F11
+CTRLKEY 0 : F12
+ Нема установок керуючих клавіш.
+ FNKEY 1..9 : F1-F9
+FNKEY 0 : F10
+FNKEY W : Up
+FNKEY A : Left
+FNKEY S : Down
+FNKEY D : Right
+FNKEY P : PageUp
+FNKEY N : PageDown
+FNKEY T : Tab
+FNKEY L : | (pipe)
+FNKEY U : _ (underscore)
+FNKEY E : Control-[ (ESC)
+FNKEY X : Delete
+FNKEY I : Insert
+FNKEY H : Home
+FNKEY F : End
+FNKEY . : Control-\\
+
+ Нема установок функціональних клавіш.
+ Закрити це вікно?
+ Виконання довільних команд в Емуляторі Терміналу
+ Дозволяє програмі відкриття нових вікон в Емуляторі Терміналу Android та запуск команд в цих вікнах з усіма дозволами Емулятора Терміналу Android, в тому числі доступ до Інтернету та SD-карти.
+ Додавання команд в Емулятор Терміналу
+ Програма зможе забезпечити додаткові команди (додати каталоги в ШЛЯХ) для емулятора терміналу Android.
+ Перевизначення команд в Емуляторі Терміналу
+ Дозволяє програмі перевизначити існуючі команди з власною версією (додайте на початку каталогів ШЛЯХ) в Емуляторі Терміналу Android.
+ Розшифрування для Емулятора Терміналу Android
+ Поштове розшифрування за допомогою:
+ Не можу вибрати поштову діяльність для відправки розшифрування.
+ Клавіша Alt посилає ESC
+ Клавіша Alt посилає ESC.
+ Клавіша Alt не посилає ESC.
+
diff --git a/term/src/main/res/values-v11/styles.xml b/term/src/main/res/values-v11/styles.xml
new file mode 100644
index 000000000..8c8825f8c
--- /dev/null
+++ b/term/src/main/res/values-v11/styles.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/term/src/main/res/values-v21/colors.xml b/term/src/main/res/values-v21/colors.xml
new file mode 100644
index 000000000..81faca8f5
--- /dev/null
+++ b/term/src/main/res/values-v21/colors.xml
@@ -0,0 +1,5 @@
+
+ #607D8B
+ #455A64
+ #8BC34A
+
diff --git a/term/src/main/res/values-v21/styles.xml b/term/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..d8c2e7d35
--- /dev/null
+++ b/term/src/main/res/values-v21/styles.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/term/src/main/res/values-zh-rCN/arrays.xml b/term/src/main/res/values-zh-rCN/arrays.xml
new file mode 100644
index 000000000..3cbebaab9
--- /dev/null
+++ b/term/src/main/res/values-zh-rCN/arrays.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+ 显示状态栏
+ 隐藏状态栏
+
+
+
+
+ 显示操作栏
+ 隐藏操作栏 (点击屏幕上方或按菜单键显示)
+
+
+
+ 自动
+ 横屏
+ 竖屏
+
+
+
+ 非闪烁光标
+ 闪烁光标
+
+
+
+ 方块
+ 下划线
+ 竖线
+
+
+
+ 4 x 8 像素
+ 6 磅
+ 7 磅
+ 8 磅
+ 9 磅
+ 10 磅
+ 12 磅
+ 14 磅
+ 16 磅
+ 20 磅
+ 24 磅
+ 28 磅
+ 32 磅
+ 36 磅
+ 42 磅
+ 48 磅
+ 64 磅
+ 72 磅
+ 96 磅
+ 144 磅
+ 288 磅
+
+
+
+ 白底黑字
+ 黑底白字
+ 蓝底白字
+ 黑底绿字
+ 黑底黄字
+ 黑底红字
+ Holo 风格
+ 爆光过度
+ 爆光不足
+ Linux 终端
+
+
+
+ 关闭所有窗口
+ 关闭当前窗口
+ 关闭当前实例,保留正在运行的会话
+ 给终端发送 ESC
+ 给终端发送 TAB
+
+
+
+ 轨迹球
+ \@ 键
+ 左 Alt 键
+ 右 Alt 键
+ 提升音量键
+ 降低音量键
+ 相机快门键
+ 无
+
+
+
+ 轨迹球
+ \@ 键
+ 左 Alt 键
+ 右 Alt 键
+ 提升音量键
+ 降低音量键
+ 相机快门键
+ 无
+
+
+
+ 字本
+ 词本
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
diff --git a/term/src/main/res/values-zh-rCN/strings.xml b/term/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000..0bb840132
--- /dev/null
+++ b/term/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,158 @@
+
+
+
+ 终端模拟器
+ 首选项
+ 新窗口
+ 关闭窗口
+ 窗口列表
+ 前一窗口
+ 后一窗口
+ 重置终端
+ 发送电子邮件到...
+ 特殊键
+ 打开/关闭软键盘
+
+ 当前窗口终端状态将被重置
+
+ 开启唤醒锁
+ 关闭唤醒锁
+ 开启 Wifi 锁
+ 关闭 Wifi 锁
+
+ 编辑文本
+ 选择文本
+ 全部复制
+ 粘贴
+ 发送 Ctrl 键
+ 发送 Fn 键
+
+ 窗口 %1$d
+
+ 会话正在运行
+
+ 会话已结束
+
+
+ 屏幕
+
+ 状态栏
+ 显示/隐藏状态栏。
+ 状态栏
+
+ 操作栏
+ 选择操作栏行为 (Android 3.0 及以上)
+ 操作栏行为
+
+ 屏幕方位
+ 选择屏幕方位行为
+ 屏幕方位行为
+
+ 光标样式
+ 选择光标样式
+ 光标样式
+
+ 光标闪烁
+ 选择光标闪烁模式
+ 光标闪烁
+
+ 文本
+
+ 默认 UTF-8
+ 是否将 UTF-8 模式设为默认
+
+ 文本大小
+ 选择文本大小
+ 文本大小
+
+ 颜色
+ 选择文本颜色
+ 文本颜色
+
+ 键盘
+
+ 返回键行为
+ 选择返回键行为
+ 返回键行为
+
+ Ctrl 键
+ 设置 Ctrl 键
+ Ctrl 键
+
+ Fn 键
+ 设置 Fn 键
+ Fn 键
+
+ 输入方式
+ 选择输入方式或软键盘
+ 输入方式
+
+ Shell
+ 命令行
+ 指定命令行使用的 Shell
+ Shell
+
+ 初始命令
+ 启动 Shell 时自动执行的命令
+ 初始命令
+
+ 终端类型
+ 设置 Shell 的终端类型
+ 终端类型
+
+ 退出时关闭窗口
+ 退出 Shell 时是否关闭窗口
+
+ 验证 PATH 路径
+ 是否将 PATH 中无效的路径清除
+
+ 允许 PATH 后扩
+ 是否允许程序添加额外命令到 PATH
+
+ 允许 PATH 前置
+ 是否允许程序覆盖 PATH 中已有命令
+
+ HOME 文件夹
+ 用作 HOME 的可写文件夹路径
+
+ Ctrl 与 Fn 键
+ CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ 未设置 Ctrl 键
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ 未设置 Fn 键
+
+ 关闭窗口吗?
+
+ 在终端模拟器中运行命令
+ 允许程序开启新窗口并运行包括与网络、存储卡相关命令在内的所有命令
+ 添加命令到终端模拟器
+ 允许程序添加额外命令
+ 覆盖终端模拟器中已有命令
+ 允许程序覆盖终端模拟器中已有命令
+
+ 终端模拟器副本
+ 发送副本的邮件实例:
+ 找不到邮件实例发送副本
+
+ Alt 键发送 ESC
+ 开启 Alt 键发送 ESC
+ 关闭 Alt 键发送 ESC
+
+ 发送鼠标事件
+ 触摸与滚屏事件是否被终端忽略
+
+
diff --git a/term/src/main/res/values-zh-rTW/arrays.xml b/term/src/main/res/values-zh-rTW/arrays.xml
new file mode 100644
index 000000000..b8c8e8340
--- /dev/null
+++ b/term/src/main/res/values-zh-rTW/arrays.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+ 顯示狀態列
+ 隱藏狀態列
+
+
+
+
+ Always show action bar
+ Hide action bar (touch top of screen or Menu key to show)
+
+
+
+ 非閃爍式浮標
+ 閃爍式浮標
+
+
+
+ 方形
+ 底線
+ 直線
+
+
+
+ 4 x 8 像素
+ 6 點
+ 7 點
+ 8 點
+ 9 點
+ 10 點
+ 12 點
+ 14 點
+ 16 點
+ 20 點
+ 24 點
+ 28 點
+ 32 點
+ 36 點
+ 42 點
+ 48 點
+ 64 點
+ 72 點
+ 96 點
+ 144 點
+ 288 點
+
+
+
+ 白底黑字
+ 黑底白字
+ 藍底白字
+ 黑底綠字
+ 黑底黄字
+ 黑底紅字
+ Holo blue on black
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Closes all terminal windows
+ Closes this terminal window only
+ Closes activity, leaving sessions running
+ Sends ESC to terminal
+ Sends TAB to terminal
+
+
+
+ 軌跡球
+ \@ 鍵
+ 左 Alt 鍵
+ 右 Alt 鍵
+ 提升音量鍵
+ 降低音量鍵
+ 相機快門鍵
+ None
+
+
+
+ 軌跡球
+ \@ 鍵
+ 左 Alt 鍵
+ 右 Alt 鍵
+ 提升音量鍵
+ 降低音量鍵
+ 相機快門鍵
+ None
+
+
+
+ 字本
+ 詞本
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
diff --git a/term/src/main/res/values-zh-rTW/strings.xml b/term/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..afda0f126
--- /dev/null
+++ b/term/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,96 @@
+
+
+
+ 模擬終端
+ 設定
+ 結束
+ 以電郵傳送
+ 特別按鍵
+ 顯示/隱藏鍵盤
+
+ 使用喚醒鎖定
+ 解除喚醒鎖定
+ 使用 WiFi 鎖定
+ 解除 WiFi 鎖定
+
+ 編輯文字
+ 選擇文字
+ 全部複製
+ 貼上
+
+ 終端機工作階段執行中
+
+
+ 螢幕
+
+ 狀態列
+ 顯示/隱藏狀態列
+ 狀態列
+
+ 浮標風格
+ 選擇浮標風格
+ 浮標風格
+
+ 浮標閃爍
+ 選擇浮標是否閃爍
+ 浮標閃爍
+
+ 文字
+
+ 字型大小
+ 選擇字元高度
+ 字型大小
+
+ 色彩
+ 選擇文字色彩
+ 文字色彩
+
+ 鍵盤
+
+ Ctrl 鍵
+ 選擇 Ctrl 鍵
+ Ctrl 鍵
+
+ Fn 鍵
+ 選擇 Fn 鍵
+ Fn 鍵
+
+ 輸入方式
+ 選擇軟體鍵盤的輸入方式
+ 輸入方式
+
+ 殼層
+ 命令列
+ 指定殼層的命令列
+ 殼層
+
+ 初始命令
+ 在啟動時傳送至殼層
+ 初始命令
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/term/src/main/res/values/arrays.xml b/term/src/main/res/values/arrays.xml
new file mode 100644
index 000000000..5f6dc2e6b
--- /dev/null
+++ b/term/src/main/res/values/arrays.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+ Show status bar
+ Hide status bar
+
+
+
+
+ Always show action bar
+ Hide action bar (touch top of screen or Menu key to show)
+
+
+
+ Automatic
+ Landscape
+ Portrait
+
+
+
+ Non-blinking cursor
+ Blinking cursor
+
+
+
+ Rectangle
+ Underline
+ Vertical bar
+
+
+
+ 4 x 8 pixels
+ 6 pt
+ 7 pt
+ 8 pt
+ 9 pt
+ 10 pt
+ 12 pt
+ 14 pt
+ 16 pt
+ 20 pt
+ 24 pt
+ 28 pt
+ 32 pt
+ 36 pt
+ 42 pt
+ 48 pt
+ 64 pt
+ 72 pt
+ 96 pt
+ 144 pt
+ 288 pt
+
+
+
+ Black text on white
+ White text on black
+ White text on blue
+ Green text on black
+ Amber text on black
+ Red text on black
+ Holo blue text on black
+ Solarized Light
+ Solarized Dark
+ Linux Console
+
+
+
+ Closes all terminal windows
+ Closes this terminal window only
+ Closes activity, leaving sessions running
+ Sends ESC to terminal
+ Sends TAB to terminal
+
+
+
+ Jog ball
+ \@ key
+ Left Alt key
+ Right Alt key
+ Vol Up key
+ Vol Down key
+ Camera key
+ None
+
+
+
+ Jog ball
+ \@ key
+ Left Alt key
+ Right Alt key
+ Vol Up key
+ Vol Down key
+ Camera key
+ None
+
+
+
+ Character-based
+ Word-based
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
+
+
+ Ball
+ \@
+ Left-Alt
+ Right-Alt
+ Vol-Up
+ Vol-Dn
+ Camera
+ None
+
+
diff --git a/res/values/arrays.xml b/term/src/main/res/values/arraysNoLocalize.xml
similarity index 54%
rename from res/values/arrays.xml
rename to term/src/main/res/values/arraysNoLocalize.xml
index 82a31bfa4..ac997a5fe 100644
--- a/res/values/arrays.xml
+++ b/term/src/main/res/values/arraysNoLocalize.xml
@@ -15,56 +15,38 @@
* limitations under the License.
-->
-
-
- Show status bar
- Hide status bar
-
+
-
+10
-
- Non-blinking cursor
- Blinking cursor
+
+
+ 1
+ 2
-
-
+ 01
+ 2
-
- Rectangle
- Underline
- Vertical bar
+
+ 0
+ 1
-
012
-
- 4 x 8 pixels
- 6 pt
- 7 pt
- 8 pt
- 9 pt
- 10 pt
- 12 pt
- 14 pt
- 16 pt
- 20 pt
-
-
-
+
06
@@ -76,18 +58,19 @@
141620
+ 24
+ 28
+ 32
+ 36
+ 42
+ 48
+ 64
+ 72
+ 96
+ 144
+ 288
-
- Black text on white
- White text on black
- White text on blue
- Green text on black
- Amber text on black
- Red text on black
-
-
-
01
@@ -95,18 +78,20 @@
345
+ 6
+ 7
+ 8
+ 9
-
- Jog ball
- \@ key
- Left Alt key
- Right Alt key
- Vol Up key
- Vol Down key
+
+ 0
+ 1
+ 2
+ 3
+ 4
-
01
@@ -114,16 +99,31 @@
345
+ 6
+ 7
-
- Character-based
- Word-based
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
-
01
+
+
+ vt100
+ screen
+ linux
+ screen-256color
+ xterm
+
diff --git a/res/values/attrs.xml b/term/src/main/res/values/attrs.xml
similarity index 100%
rename from res/values/attrs.xml
rename to term/src/main/res/values/attrs.xml
diff --git a/term/src/main/res/values/defaults.xml b/term/src/main/res/values/defaults.xml
new file mode 100644
index 000000000..9214dd631
--- /dev/null
+++ b/term/src/main/res/values/defaults.xml
@@ -0,0 +1,27 @@
+
+
+
+ 1
+ 1
+ 0
+ 0
+ 0
+ 10
+ 1
+ false
+ 2
+ 5
+ 4
+ 0
+ false
+ /system/bin/sh -
+
+ screen
+ true
+ true
+ true
+ true
+
+ false
+ true
+
diff --git a/term/src/main/res/values/id.xml b/term/src/main/res/values/id.xml
new file mode 100644
index 000000000..342991ebf
--- /dev/null
+++ b/term/src/main/res/values/id.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/term/src/main/res/values/strings.xml b/term/src/main/res/values/strings.xml
new file mode 100644
index 000000000..3ca392708
--- /dev/null
+++ b/term/src/main/res/values/strings.xml
@@ -0,0 +1,197 @@
+
+
+
+ Terminal Emulator
+ Preferences
+ New window
+ Close window
+ Windows
+ Prev window
+ Next window
+ Reset term
+ Email to
+ Special keys
+ Toggle soft keyboard
+
+ This window\'s terminal state has been reset.
+
+ Take WakeLock
+ Drop WakeLock
+ Take WifiLock
+ Drop WifiLock
+
+ Edit text
+ Select text
+ Copy all
+ Paste
+ Send control key
+ Send fn key
+
+ Window %1$d
+
+ Terminal session is running
+
+ Terminal session finished
+
+
+ Screen
+
+ Status bar
+ Show/hide status bar.
+ Status bar
+
+ Action bar
+ Choose the action bar\'s behavior (Android 3.0 and up).
+ Action bar behavior
+
+ Screen orientation
+ Choose the screen orientation behavior.
+ Screen orientation behavior
+
+ Cursor style
+ Choose cursor style.
+ Cursor style
+
+ Cursor blink
+ Choose cursor blink.
+ Cursor blink
+
+ Text
+
+ Default to UTF-8
+ Whether UTF-8 mode is enabled by default.
+
+ Font size
+ Choose character height in points.
+ Font size
+
+ Colors
+ Choose text color.
+ Text color
+
+ Keyboard
+
+ Back button behavior
+ Choose what pressing the back button does.
+ Back button behavior
+
+ Control key
+ Choose control key.
+ Control key
+
+ Fn key
+ Choose Fn key.
+ Fn key
+
+ Input method
+ Choose input method for soft keyboard.
+ Input method
+
+ Shell
+ Command line
+ Specify the shell command line.
+ Shell
+
+ Initial command
+ Sent to the shell when it starts.
+ Initial Command
+
+ Terminal type
+ What terminal type to report to the shell.
+ Terminal type
+
+ Close window on exit
+ Whether a window should close when its shell exits.
+
+ Verify PATH entries
+ Whether inaccessible directories should be removed from the PATH.
+
+ Allow PATH extensions
+ Whether other apps should be allowed to provide additional commands (add to PATH).
+
+ Allow PATH prepend
+ Whether other apps should be allowed to override existing commands (add to the beginning of PATH).
+
+ HOME folder
+ Path to a writable folder to be used as HOME.
+
+ Control and Function Keys
+
+ CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12
+ No control key set.
+
+ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n
+ No function key set.
+
+ Close this window?
+
+ Run arbitrary commands in Terminal Emulator
+ Allows application to open new windows in Terminal Emulator for Android and run commands in those windows with all of Terminal Emulator for Android\'s permissions, including access to the Internet and your SD Card.
+ Add commands to Terminal Emulator
+ Allows application to provide additional commands (add directories to the PATH) for Terminal Emulator for Android.
+ Override commands in Terminal Emulator
+ Allows application to override existing commands with its own versions (prepend directories to the PATH) in Terminal Emulator for Android.
+
+ Transcript from Terminal Emulator for Android
+ Mail transcript using:
+ Could not choose an email activity to send transcript.
+
+ Alt key sends ESC
+ Alt key sends ESC.
+ Alt key does not send ESC.
+
+ Send mouse events
+ Whether tap and scroll events should be sent as escape sequences to the terminal.
+
+ Use Keyboard Shortcuts
+ Ctrl-Tab: cycle window, Ctrl-Shift-N: new window, Ctrl-Shift-V: paste.
+ Keyboard shortcuts disabled.
+
+ Help
+ http://jackpal.github.com/Android-Terminal-Emulator/help/index.html
+ Term here
+
+
+ "Term shortcut"
+
+ FILE SELECTOR
+ External storage not available
+ Or enter path here.
+ Change theme
+
+ command
+ --example=\""a\"
+ Find command
+ SELECT SHORTCUT TARGET
+ Command window requires full path, no arguments. For other commands use Arguments window (ex: cd /sdcard).
+ Arguments\:
+ Shortcut label\:
+ Text icon
+ MAKE TEXT ICON
+
+ Term shortcut
+
+ α
+ R
+ G
+ B
+ LOCK
+ Enter icon text
+
+
diff --git a/res/values/styles.xml b/term/src/main/res/values/styles.xml
similarity index 86%
rename from res/values/styles.xml
rename to term/src/main/res/values/styles.xml
index 0e59f4a76..3f942c433 100644
--- a/res/values/styles.xml
+++ b/term/src/main/res/values/styles.xml
@@ -4,9 +4,9 @@
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.
@@ -14,10 +14,14 @@
limitations under the License.
-->
+
+
diff --git a/term/src/main/res/xml/preferences.xml b/term/src/main/res/xml/preferences.xml
new file mode 100644
index 000000000..e44b2670a
--- /dev/null
+++ b/term/src/main/res/xml/preferences.xml
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/controlSequences/256color.txt b/tests/controlSequences/256color.txt
new file mode 100644
index 000000000..fb186559d
--- /dev/null
+++ b/tests/controlSequences/256color.txt
@@ -0,0 +1,13 @@
+]4;16;rgb:00/00/00\]4;17;rgb:00/00/5f\]4;18;rgb:00/00/87\]4;19;rgb:00/00/af\]4;20;rgb:00/00/d7\]4;21;rgb:00/00/ff\]4;22;rgb:00/5f/00\]4;23;rgb:00/5f/5f\]4;24;rgb:00/5f/87\]4;25;rgb:00/5f/af\]4;26;rgb:00/5f/d7\]4;27;rgb:00/5f/ff\]4;28;rgb:00/87/00\]4;29;rgb:00/87/5f\]4;30;rgb:00/87/87\]4;31;rgb:00/87/af\]4;32;rgb:00/87/d7\]4;33;rgb:00/87/ff\]4;34;rgb:00/af/00\]4;35;rgb:00/af/5f\]4;36;rgb:00/af/87\]4;37;rgb:00/af/af\]4;38;rgb:00/af/d7\]4;39;rgb:00/af/ff\]4;40;rgb:00/d7/00\]4;41;rgb:00/d7/5f\]4;42;rgb:00/d7/87\]4;43;rgb:00/d7/af\]4;44;rgb:00/d7/d7\]4;45;rgb:00/d7/ff\]4;46;rgb:00/ff/00\]4;47;rgb:00/ff/5f\]4;48;rgb:00/ff/87\]4;49;rgb:00/ff/af\]4;50;rgb:00/ff/d7\]4;51;rgb:00/ff/ff\]4;52;rgb:5f/00/00\]4;53;rgb:5f/00/5f\]4;54;rgb:5f/00/87\]4;55;rgb:5f/00/af\]4;56;rgb:5f/00/d7\]4;57;rgb:5f/00/ff\]4;58;rgb:5f/5f/00\]4;59;rgb:5f/5f/5f\]4;60;rgb:5f/5f/87\]4;61;rgb:5f/5f/af\]4;62;rgb:5f/5f/d7\]4;63;rgb:5f/5f/ff\]4;64;rgb:5f/87/00\]4;65;rgb:5f/87/5f\]4;66;rgb:5f/87/87\]4;67;rgb:5f/87/af\]4;68;rgb:5f/87/d7\]4;69;rgb:5f/87/ff\]4;70;rgb:5f/af/00\]4;71;rgb:5f/af/5f\]4;72;rgb:5f/af/87\]4;73;rgb:5f/af/af\]4;74;rgb:5f/af/d7\]4;75;rgb:5f/af/ff\]4;76;rgb:5f/d7/00\]4;77;rgb:5f/d7/5f\]4;78;rgb:5f/d7/87\]4;79;rgb:5f/d7/af\]4;80;rgb:5f/d7/d7\]4;81;rgb:5f/d7/ff\]4;82;rgb:5f/ff/00\]4;83;rgb:5f/ff/5f\]4;84;rgb:5f/ff/87\]4;85;rgb:5f/ff/af\]4;86;rgb:5f/ff/d7\]4;87;rgb:5f/ff/ff\]4;88;rgb:87/00/00\]4;89;rgb:87/00/5f\]4;90;rgb:87/00/87\]4;91;rgb:87/00/af\]4;92;rgb:87/00/d7\]4;93;rgb:87/00/ff\]4;94;rgb:87/5f/00\]4;95;rgb:87/5f/5f\]4;96;rgb:87/5f/87\]4;97;rgb:87/5f/af\]4;98;rgb:87/5f/d7\]4;99;rgb:87/5f/ff\]4;100;rgb:87/87/00\]4;101;rgb:87/87/5f\]4;102;rgb:87/87/87\]4;103;rgb:87/87/af\]4;104;rgb:87/87/d7\]4;105;rgb:87/87/ff\]4;106;rgb:87/af/00\]4;107;rgb:87/af/5f\]4;108;rgb:87/af/87\]4;109;rgb:87/af/af\]4;110;rgb:87/af/d7\]4;111;rgb:87/af/ff\]4;112;rgb:87/d7/00\]4;113;rgb:87/d7/5f\]4;114;rgb:87/d7/87\]4;115;rgb:87/d7/af\]4;116;rgb:87/d7/d7\]4;117;rgb:87/d7/ff\]4;118;rgb:87/ff/00\]4;119;rgb:87/ff/5f\]4;120;rgb:87/ff/87\]4;121;rgb:87/ff/af\]4;122;rgb:87/ff/d7\]4;123;rgb:87/ff/ff\]4;124;rgb:af/00/00\]4;125;rgb:af/00/5f\]4;126;rgb:af/00/87\]4;127;rgb:af/00/af\]4;128;rgb:af/00/d7\]4;129;rgb:af/00/ff\]4;130;rgb:af/5f/00\]4;131;rgb:af/5f/5f\]4;132;rgb:af/5f/87\]4;133;rgb:af/5f/af\]4;134;rgb:af/5f/d7\]4;135;rgb:af/5f/ff\]4;136;rgb:af/87/00\]4;137;rgb:af/87/5f\]4;138;rgb:af/87/87\]4;139;rgb:af/87/af\]4;140;rgb:af/87/d7\]4;141;rgb:af/87/ff\]4;142;rgb:af/af/00\]4;143;rgb:af/af/5f\]4;144;rgb:af/af/87\]4;145;rgb:af/af/af\]4;146;rgb:af/af/d7\]4;147;rgb:af/af/ff\]4;148;rgb:af/d7/00\]4;149;rgb:af/d7/5f\]4;150;rgb:af/d7/87\]4;151;rgb:af/d7/af\]4;152;rgb:af/d7/d7\]4;153;rgb:af/d7/ff\]4;154;rgb:af/ff/00\]4;155;rgb:af/ff/5f\]4;156;rgb:af/ff/87\]4;157;rgb:af/ff/af\]4;158;rgb:af/ff/d7\]4;159;rgb:af/ff/ff\]4;160;rgb:d7/00/00\]4;161;rgb:d7/00/5f\]4;162;rgb:d7/00/87\]4;163;rgb:d7/00/af\]4;164;rgb:d7/00/d7\]4;165;rgb:d7/00/ff\]4;166;rgb:d7/5f/00\]4;167;rgb:d7/5f/5f\]4;168;rgb:d7/5f/87\]4;169;rgb:d7/5f/af\]4;170;rgb:d7/5f/d7\]4;171;rgb:d7/5f/ff\]4;172;rgb:d7/87/00\]4;173;rgb:d7/87/5f\]4;174;rgb:d7/87/87\]4;175;rgb:d7/87/af\]4;176;rgb:d7/87/d7\]4;177;rgb:d7/87/ff\]4;178;rgb:d7/af/00\]4;179;rgb:d7/af/5f\]4;180;rgb:d7/af/87\]4;181;rgb:d7/af/af\]4;182;rgb:d7/af/d7\]4;183;rgb:d7/af/ff\]4;184;rgb:d7/d7/00\]4;185;rgb:d7/d7/5f\]4;186;rgb:d7/d7/87\]4;187;rgb:d7/d7/af\]4;188;rgb:d7/d7/d7\]4;189;rgb:d7/d7/ff\]4;190;rgb:d7/ff/00\]4;191;rgb:d7/ff/5f\]4;192;rgb:d7/ff/87\]4;193;rgb:d7/ff/af\]4;194;rgb:d7/ff/d7\]4;195;rgb:d7/ff/ff\]4;196;rgb:ff/00/00\]4;197;rgb:ff/00/5f\]4;198;rgb:ff/00/87\]4;199;rgb:ff/00/af\]4;200;rgb:ff/00/d7\]4;201;rgb:ff/00/ff\]4;202;rgb:ff/5f/00\]4;203;rgb:ff/5f/5f\]4;204;rgb:ff/5f/87\]4;205;rgb:ff/5f/af\]4;206;rgb:ff/5f/d7\]4;207;rgb:ff/5f/ff\]4;208;rgb:ff/87/00\]4;209;rgb:ff/87/5f\]4;210;rgb:ff/87/87\]4;211;rgb:ff/87/af\]4;212;rgb:ff/87/d7\]4;213;rgb:ff/87/ff\]4;214;rgb:ff/af/00\]4;215;rgb:ff/af/5f\]4;216;rgb:ff/af/87\]4;217;rgb:ff/af/af\]4;218;rgb:ff/af/d7\]4;219;rgb:ff/af/ff\]4;220;rgb:ff/d7/00\]4;221;rgb:ff/d7/5f\]4;222;rgb:ff/d7/87\]4;223;rgb:ff/d7/af\]4;224;rgb:ff/d7/d7\]4;225;rgb:ff/d7/ff\]4;226;rgb:ff/ff/00\]4;227;rgb:ff/ff/5f\]4;228;rgb:ff/ff/87\]4;229;rgb:ff/ff/af\]4;230;rgb:ff/ff/d7\]4;231;rgb:ff/ff/ff\]4;232;rgb:08/08/08\]4;233;rgb:12/12/12\]4;234;rgb:1c/1c/1c\]4;235;rgb:26/26/26\]4;236;rgb:30/30/30\]4;237;rgb:3a/3a/3a\]4;238;rgb:44/44/44\]4;239;rgb:4e/4e/4e\]4;240;rgb:58/58/58\]4;241;rgb:62/62/62\]4;242;rgb:6c/6c/6c\]4;243;rgb:76/76/76\]4;244;rgb:80/80/80\]4;245;rgb:8a/8a/8a\]4;246;rgb:94/94/94\]4;247;rgb:9e/9e/9e\]4;248;rgb:a8/a8/a8\]4;249;rgb:b2/b2/b2\]4;250;rgb:bc/bc/bc\]4;251;rgb:c6/c6/c6\]4;252;rgb:d0/d0/d0\]4;253;rgb:da/da/da\]4;254;rgb:e4/e4/e4\]4;255;rgb:ee/ee/ee\System colors:
+[48;5;0m [48;5;1m [48;5;2m [48;5;3m [48;5;4m [48;5;5m [48;5;6m [48;5;7m [0m
+[48;5;8m [48;5;9m [48;5;10m [48;5;11m [48;5;12m [48;5;13m [48;5;14m [48;5;15m [0m
+
+Color cube, 6x6x6:
+[48;5;16m [48;5;17m [48;5;18m [48;5;19m [48;5;20m [48;5;21m [0m [48;5;52m [48;5;53m [48;5;54m [48;5;55m [48;5;56m [48;5;57m [0m [48;5;88m [48;5;89m [48;5;90m [48;5;91m [48;5;92m [48;5;93m [0m [48;5;124m [48;5;125m [48;5;126m [48;5;127m [48;5;128m [48;5;129m [0m [48;5;160m [48;5;161m [48;5;162m [48;5;163m [48;5;164m [48;5;165m [0m [48;5;196m [48;5;197m [48;5;198m [48;5;199m [48;5;200m [48;5;201m [0m
+[48;5;22m [48;5;23m [48;5;24m [48;5;25m [48;5;26m [48;5;27m [0m [48;5;58m [48;5;59m [48;5;60m [48;5;61m [48;5;62m [48;5;63m [0m [48;5;94m [48;5;95m [48;5;96m [48;5;97m [48;5;98m [48;5;99m [0m [48;5;130m [48;5;131m [48;5;132m [48;5;133m [48;5;134m [48;5;135m [0m [48;5;166m [48;5;167m [48;5;168m [48;5;169m [48;5;170m [48;5;171m [0m [48;5;202m [48;5;203m [48;5;204m [48;5;205m [48;5;206m [48;5;207m [0m
+[48;5;28m [48;5;29m [48;5;30m [48;5;31m [48;5;32m [48;5;33m [0m [48;5;64m [48;5;65m [48;5;66m [48;5;67m [48;5;68m [48;5;69m [0m [48;5;100m [48;5;101m [48;5;102m [48;5;103m [48;5;104m [48;5;105m [0m [48;5;136m [48;5;137m [48;5;138m [48;5;139m [48;5;140m [48;5;141m [0m [48;5;172m [48;5;173m [48;5;174m [48;5;175m [48;5;176m [48;5;177m [0m [48;5;208m [48;5;209m [48;5;210m [48;5;211m [48;5;212m [48;5;213m [0m
+[48;5;34m [48;5;35m [48;5;36m [48;5;37m [48;5;38m [48;5;39m [0m [48;5;70m [48;5;71m [48;5;72m [48;5;73m [48;5;74m [48;5;75m [0m [48;5;106m [48;5;107m [48;5;108m [48;5;109m [48;5;110m [48;5;111m [0m [48;5;142m [48;5;143m [48;5;144m [48;5;145m [48;5;146m [48;5;147m [0m [48;5;178m [48;5;179m [48;5;180m [48;5;181m [48;5;182m [48;5;183m [0m [48;5;214m [48;5;215m [48;5;216m [48;5;217m [48;5;218m [48;5;219m [0m
+[48;5;40m [48;5;41m [48;5;42m [48;5;43m [48;5;44m [48;5;45m [0m [48;5;76m [48;5;77m [48;5;78m [48;5;79m [48;5;80m [48;5;81m [0m [48;5;112m [48;5;113m [48;5;114m [48;5;115m [48;5;116m [48;5;117m [0m [48;5;148m [48;5;149m [48;5;150m [48;5;151m [48;5;152m [48;5;153m [0m [48;5;184m [48;5;185m [48;5;186m [48;5;187m [48;5;188m [48;5;189m [0m [48;5;220m [48;5;221m [48;5;222m [48;5;223m [48;5;224m [48;5;225m [0m
+[48;5;46m [48;5;47m [48;5;48m [48;5;49m [48;5;50m [48;5;51m [0m [48;5;82m [48;5;83m [48;5;84m [48;5;85m [48;5;86m [48;5;87m [0m [48;5;118m [48;5;119m [48;5;120m [48;5;121m [48;5;122m [48;5;123m [0m [48;5;154m [48;5;155m [48;5;156m [48;5;157m [48;5;158m [48;5;159m [0m [48;5;190m [48;5;191m [48;5;192m [48;5;193m [48;5;194m [48;5;195m [0m [48;5;226m [48;5;227m [48;5;228m [48;5;229m [48;5;230m [48;5;231m [0m
+Grayscale ramp:
+[48;5;232m [48;5;233m [48;5;234m [48;5;235m [48;5;236m [48;5;237m [48;5;238m [48;5;239m [48;5;240m [48;5;241m [48;5;242m [48;5;243m [48;5;244m [48;5;245m [48;5;246m [48;5;247m [48;5;248m [48;5;249m [48;5;250m [48;5;251m [48;5;252m [48;5;253m [48;5;254m [48;5;255m [0m
diff --git a/tests/controlSequences/combiningCharReplacement.txt b/tests/controlSequences/combiningCharReplacement.txt
new file mode 100644
index 000000000..16230d9ba
--- /dev/null
+++ b/tests/controlSequences/combiningCharReplacement.txt
@@ -0,0 +1,10 @@
+Overwriting a letter with combining diacritic with a letter without in an
+80-column line:
+
+xxxxxMu[combining diaeresis]nchenxxx[...] ESC [ 7 G u:
+xxxxxMünchenxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx[7Gu
+
+Overwriting a letter without combining diacritic with a letter with combining diacritic in an 80-column line:
+
+xxxxMunchenxxx[...] ESC [ 6 G u [combining diaeresis]:
+xxxxMunchenxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx[6Gü
diff --git a/tests/controlSequences/combiningChars.txt b/tests/controlSequences/combiningChars.txt
new file mode 100644
index 000000000..9f0915cc5
--- /dev/null
+++ b/tests/controlSequences/combiningChars.txt
@@ -0,0 +1,6 @@
+a combining grave accent: à
+a combining grave accent combining low line: à̲
+a combining enclosing circle backslash: à̲⃠
+musical symbol g clef: 𝄞
+
+a combining grave accent combining low line at the end of an 80-column line: à̲
diff --git a/tests/controlSequences/hideCursor.txt b/tests/controlSequences/hideCursor.txt
new file mode 100644
index 000000000..2f21b2a60
--- /dev/null
+++ b/tests/controlSequences/hideCursor.txt
@@ -0,0 +1 @@
+[?25lThe cursor is now hidden.
diff --git a/tests/controlSequences/setTitle.txt b/tests/controlSequences/setTitle.txt
new file mode 100644
index 000000000..556a1d6d7
--- /dev/null
+++ b/tests/controlSequences/setTitle.txt
@@ -0,0 +1 @@
+]0;I am a title\Window title should now be 'I am a title'.
diff --git a/tests/controlSequences/showCursor.txt b/tests/controlSequences/showCursor.txt
new file mode 100644
index 000000000..7543e096e
--- /dev/null
+++ b/tests/controlSequences/showCursor.txt
@@ -0,0 +1 @@
+[?25hThe cursor is now visible.
diff --git a/tests/controlSequences/textStyle.txt b/tests/controlSequences/textStyle.txt
new file mode 100644
index 000000000..c526b58b5
--- /dev/null
+++ b/tests/controlSequences/textStyle.txt
@@ -0,0 +1,20 @@
+Text style test
+normal: [0mtext[0m
+bold: [1mtext[0m
+faint: [2mtext[0m
+italic: [3mtext[0m (may show as inverse)
+underline: [4mtext[0m
+blink: [5mtext[0m (may show as bold)
+6: [6mtext[0m
+inverse: [7mtext[0m
+invisible: [8mtext[0m
+bold-underline: [1;4mtext[0m
+bold-ul-inverse: [1;4;7mtext[0m
+invis-bold-ul: [1;4;8mtext[0m
+invis-bold-ul-invrs: [1;4;7;8mtext[0m
+everything: [1;2;3;4;5;6;7;8mtext[0m
+Reset bold/faint: [1;2;3;4;5;6;7mtext[22mtext[23;24;25;27mtext[0m
+Reset bold/faint: [1mtext[22mtext[0m
+Reset bold/faint: [2mtext[22mtext[0m
+dim colors: [31;42mred on green[0m
+bright colors: [91;102mred on green[0m
diff --git a/tests/emulatorview-test/AndroidManifest.xml b/tests/emulatorview-test/AndroidManifest.xml
new file mode 100644
index 000000000..77f9dcf99
--- /dev/null
+++ b/tests/emulatorview-test/AndroidManifest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/emulatorview-test/ant.properties b/tests/emulatorview-test/ant.properties
new file mode 100644
index 000000000..15722404a
--- /dev/null
+++ b/tests/emulatorview-test/ant.properties
@@ -0,0 +1,18 @@
+# This file is used to override default values used by the Ant build system.
+#
+# This file must be checked into Version Control Systems, as it is
+# integral to the build system of your project.
+
+# This file is only used by the Ant script.
+
+# You can use this to override default values such as
+# 'source.dir' for the location of your java source folder and
+# 'out.dir' for the location of your output folder.
+
+# You can also use it define how the release builds are signed by declaring
+# the following properties:
+# 'key.store' for the location of your keystore and
+# 'key.alias' for the name of the key to use.
+# The password will be asked during the build when you use the 'release' target.
+
+tested.project.dir=../..
diff --git a/tests/emulatorview-test/proguard-project.txt b/tests/emulatorview-test/proguard-project.txt
new file mode 100644
index 000000000..f2fe1559a
--- /dev/null
+++ b/tests/emulatorview-test/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/tests/emulatorview-test/project.properties b/tests/emulatorview-test/project.properties
new file mode 100644
index 000000000..4d07452b5
--- /dev/null
+++ b/tests/emulatorview-test/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-11
diff --git a/tests/emulatorview-test/res/drawable-hdpi/ic_launcher.png b/tests/emulatorview-test/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..96a442e5b
Binary files /dev/null and b/tests/emulatorview-test/res/drawable-hdpi/ic_launcher.png differ
diff --git a/tests/emulatorview-test/res/drawable-ldpi/ic_launcher.png b/tests/emulatorview-test/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 000000000..99238729d
Binary files /dev/null and b/tests/emulatorview-test/res/drawable-ldpi/ic_launcher.png differ
diff --git a/tests/emulatorview-test/res/drawable-mdpi/ic_launcher.png b/tests/emulatorview-test/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..359047dfa
Binary files /dev/null and b/tests/emulatorview-test/res/drawable-mdpi/ic_launcher.png differ
diff --git a/tests/emulatorview-test/res/drawable-xhdpi/ic_launcher.png b/tests/emulatorview-test/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..71c6d760f
Binary files /dev/null and b/tests/emulatorview-test/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/tests/emulatorview-test/res/values/strings.xml b/tests/emulatorview-test/res/values/strings.xml
new file mode 100644
index 000000000..4d3121dc4
--- /dev/null
+++ b/tests/emulatorview-test/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+
+ Emulatorview-testTest
+
+
\ No newline at end of file
diff --git a/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/InputConnectionTest.java b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/InputConnectionTest.java
new file mode 100644
index 000000000..3d2caa255
--- /dev/null
+++ b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/InputConnectionTest.java
@@ -0,0 +1,23 @@
+package jackpal.androidterm.emulatorview;
+
+import android.test.AndroidTestCase;
+// import android.test.mock.MockApplication;
+// import android.test.mock.MockContext;
+// import android.util.DisplayMetrics;
+// import android.view.inputmethod.InputConnection;
+
+public class InputConnectionTest extends AndroidTestCase {
+
+ public InputConnectionTest() {
+ super();
+ }
+
+ public void testBackSpace() {
+// MockContext cn = new MockContext();
+// MockApplication a = new MockApplication();
+// TermSession ts = new TermSession();
+// DisplayMetrics m = new DisplayMetrics();
+// EmulatorView e = new EmulatorView(cn, ts, m);
+// InputConnection c = e.onCreateInputConnection(null);
+ }
+}
diff --git a/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/ModifierKeyTest.java b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/ModifierKeyTest.java
new file mode 100644
index 000000000..e8077774c
--- /dev/null
+++ b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/ModifierKeyTest.java
@@ -0,0 +1,14 @@
+package jackpal.androidterm.emulatorview;
+
+import android.test.AndroidTestCase;
+// import android.view.KeyEvent;
+
+public class ModifierKeyTest extends AndroidTestCase {
+
+ public void testCtrlKey() {
+// ModifierKey mk = new ModifierKey(KeyEvent.KEYCODE_CTRL_LEFT);
+// assertFalse(mk.isActive());
+// mk.handleModifierKey(KeyEvent.KEYCODE_CTRL_LEFT, true);
+// assertTrue(mk.isActive());
+ }
+}
diff --git a/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/TermKeyListenerTest.java b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/TermKeyListenerTest.java
new file mode 100644
index 000000000..27789bda5
--- /dev/null
+++ b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/TermKeyListenerTest.java
@@ -0,0 +1,433 @@
+package jackpal.androidterm.emulatorview;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+import android.annotation.TargetApi;
+import android.test.AndroidTestCase;
+import android.view.KeyEvent;
+
+class MockTermSession extends TermSession {
+
+ private byte[] charseq = null;
+
+ @Override
+ public void write(byte[] data, int offset, int count) {
+ if (charseq==null) {
+ charseq = data;
+ } else {
+ byte[] tmp = new byte[data.length + charseq.length];
+ for (int i = 0; i < charseq.length; i++) {
+ tmp[i] = charseq[i];
+ }
+ for (int i = 0; i < data.length; i++) {
+ tmp[i+charseq.length] = data[i];
+ }
+ charseq = tmp;
+ }
+ }
+
+ public void clearQueue() {
+ charseq = null;
+ }
+
+ public byte[] getCharSequence() {
+ return charseq;
+ }
+
+ @Override
+ public void write(String data) {
+ try {
+ byte[] bytes = data.getBytes("UTF-8");
+ write(bytes, 0, bytes.length);
+ } catch (UnsupportedEncodingException e) {
+ }
+ }
+
+ @Override
+ public void write(int codePoint) {
+ CharBuffer charBuf = CharBuffer.allocate(2);
+ ByteBuffer byteBuf = ByteBuffer.allocate(4);
+ CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
+
+ charBuf.clear();
+ byteBuf.clear();
+ Character.toChars(codePoint, charBuf.array(), 0);
+ encoder.reset();
+ encoder.encode(charBuf, byteBuf, true);
+ encoder.flush(byteBuf);
+ write(byteBuf.array(), 0, byteBuf.position()-1);
+ }
+
+}
+
+@TargetApi(3)
+public class TermKeyListenerTest extends AndroidTestCase {
+ //TermKeyListener tkl_AltIsEsc;
+ TermKeyListener tkl_AltNotEsc;
+ //MockTermSession mckTermSessionA = new MockTermSession();
+ MockTermSession mckTermSessionB = new MockTermSession();
+ public TermKeyListenerTest() {
+ super();
+ }
+
+ public void setUp() {
+ //tkl_AltIsEsc = new TermKeyListener(mckTermSessionA);
+ tkl_AltNotEsc = new TermKeyListener(mckTermSessionB);
+ mckTermSessionB.clearQueue();
+ }
+
+ public void testKey_a()
+ throws UnsupportedEncodingException, IOException {
+ keyHelper(KeyEvent.KEYCODE_A,
+ 0,
+ new byte[]{0x61});
+ }
+
+ public void testKey_X()
+ throws UnsupportedEncodingException, IOException {
+ keyHelper(KeyEvent.KEYCODE_X,
+ KeyEvent.META_SHIFT_ON,
+ new byte[]{0x58});
+ }
+
+ public void testKey_CTRL_c()
+ throws UnsupportedEncodingException, IOException {
+ keyHelper(KeyEvent.KEYCODE_C,
+
+ KeyEvent.META_CTRL_ON ,
+ new byte[]{0x03});
+ }
+
+ public void testKey_ALT_x_no_esc()
+ throws UnsupportedEncodingException, IOException {
+ keyHelper(KeyEvent.KEYCODE_X,
+
+ KeyEvent.META_ALT_ON,
+ new byte[]{0x00,0x00,0x00,0x00});
+ }
+
+ public void testKey_ALT_x_esc()
+ throws UnsupportedEncodingException, IOException {
+ tkl_AltNotEsc.setAltSendsEsc(true);
+ int keycode = KeyEvent.KEYCODE_ALT_LEFT;
+ KeyEvent event = new KeyEvent(1,2, KeyEvent.ACTION_DOWN, keycode, 0,
+ 0);
+ tkl_AltNotEsc.keyDown(keycode, event, false, true);
+ keyHelperToggle(KeyEvent.KEYCODE_X,
+
+ KeyEvent.META_ALT_ON,
+ new byte[]{0x1b,0x78}, true);
+ }
+
+ public void testKey_ALT_c_no_esc()
+ throws UnsupportedEncodingException, IOException {
+ keyHelper(KeyEvent.KEYCODE_C,
+
+ KeyEvent.META_ALT_ON,
+ new byte[]{-61,-89,0x00,0x00});
+ }
+
+
+ public void testKey_enter()
+ throws UnsupportedEncodingException, IOException {
+ keyHelper(KeyEvent.KEYCODE_ENTER,
+ 0,
+ new byte[]{0x0d});
+ }
+
+
+ public void testKey_del()
+ throws UnsupportedEncodingException, IOException {
+ KeyEvent event = new KeyEvent(1,2, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DEL,0 ,0);
+ tkl_AltNotEsc.keyDown(event.getKeyCode(), event, true, false);
+ byte[] res = mckTermSessionB.getCharSequence();
+ byte[] exp = "\177".getBytes("UTF-8");
+ assertNotNull(res);
+ assertEquals(exp.length, res.length);
+ for (int i = 0; iissue145repro.txt
+
+This is a minimal repro case for
+
+https://github.com/jackpal/Android-Terminal-Emulator/issues/145
+
+Repro steps
+-----------
+
+On a PC:
+
+adb shell mkdir /data/local
+adb push issue145repro.txt /data/local/issue145repro.txt
+
+Run ATE
+
+Configure ATE's preferences for UTF8
+
+On ATE:
+
+cat /data/local/issue145repro.txt
diff --git a/tests/issue145/fuzzer.go b/tests/issue145/fuzzer.go
new file mode 100644
index 000000000..250a42eea
--- /dev/null
+++ b/tests/issue145/fuzzer.go
@@ -0,0 +1,37 @@
+// Generate a repatable string of characters for fuzz testing.
+
+package main
+
+import (
+ "fmt"
+ "math/rand"
+ "unicode"
+)
+
+func main() {
+ source := rand.NewSource(17)
+ r := rand.New(source)
+
+ for {
+ switch r.Intn(4) {
+ case 0:
+ if r.Intn(20) == 0 {
+ fmt.Printf("\n")
+ } else {
+ out(r, 32, 128)
+ }
+ case 1:
+ out(r, 128, 256)
+ case 2:
+ out(r, 256, 0x10000)
+ default:
+ out(r, 0x10000, unicode.MaxRune)
+ }
+ }
+}
+
+func out(r *rand.Rand, begin, end int) {
+ var c rune
+ c = (rune)(r.Intn(end-begin) + begin)
+ fmt.Printf("%c", c)
+}
diff --git a/tests/issue145/issue145repro-2.txt b/tests/issue145/issue145repro-2.txt
new file mode 100644
index 000000000..ff447c9dd
--- /dev/null
+++ b/tests/issue145/issue145repro-2.txt
@@ -0,0 +1 @@
+a8珮蠟茤퓉�B應Z
\ No newline at end of file
diff --git a/tests/issue145/issue145repro.txt b/tests/issue145/issue145repro.txt
new file mode 100644
index 000000000..d5ed584bd
--- /dev/null
+++ b/tests/issue145/issue145repro.txt
@@ -0,0 +1,2 @@
+𬘴
+(
\ No newline at end of file
diff --git a/tests/issue149/colors.go b/tests/issue149/colors.go
new file mode 100644
index 000000000..4f055dc08
--- /dev/null
+++ b/tests/issue149/colors.go
@@ -0,0 +1,30 @@
+// Generate a repatable string of characters for fuzz testing.
+
+package main
+
+import (
+ "fmt"
+)
+
+func setFore(c int) {
+ fmt.Printf("\x1b[38;5;%dm", c)
+}
+
+func setBack(c int) {
+ fmt.Printf("\x1b[48;5;%dm", c)
+}
+
+func main() {
+ /*
+ for i := 0; i < 255; i++ {
+ setFore(i)
+ fmt.Printf("Fore Color %d \n", i)
+ }
+ setFore(0)
+ */
+
+ for i := 0; i < 255; i++ {
+ setBack(i)
+ fmt.Printf("Back Color %d \n", i)
+ }
+}
diff --git a/tests/wideChars/combining-chars.txt b/tests/wideChars/combining-chars.txt
new file mode 100644
index 000000000..fb777ceff
--- /dev/null
+++ b/tests/wideChars/combining-chars.txt
@@ -0,0 +1,57 @@
+East Asian wide characters with combining diacritics:
+
+中文 [combining low line]:
+中文̲
+
+xxx中[combining low line]文:
+xxx中̲文
+
+Overwriting an East Asian wide character with combining diacritic with an East
+Asian wide character without combining diacritic, in the middle of an 80-column
+line with mixed narrow and wide chars:
+
+xxx[...]xxx中文[combining low line]xxx[...]xxx你好ESC [ 23 G 国:
+xxxxxxxxxxxxxxxxxxxx中文̲xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[23G国
+
+xxx[...]xxx中文[combining low line]。xxx[...]xxx你好xESC [ 23 G 国:
+xxxxxxxxxxxxxxxxxxxx中文̲。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[23G国
+
+Overwriting an East Asian wide character with another one with a combining
+diacritic, in the middle of an 80-column line with mixed narrow and wide chars:
+
+xxx[...]xxx中文xxx[...]xxx你好ESC [ 23 G 国[combining low line]:
+xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[23G国̲
+
+xxx[...]xxx中文。xxx[...]xxx你好xESC [ 23 G 国[combining low line]:
+xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[23G国̲
+
+Overwriting a narrow character with an East Asian wide character with a
+combining diacritic, in the middle of an 80-column line with mixed narrow and
+wide chars:
+
+xxx[...]xxx中文xxx[...]xxx你好xESC [ 25 G 。[combining low line]:
+xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[25G。̲
+
+xxx[...]xxx中y文xxx[...]xxx你好ESC [ 23 G ,[combining low line]:
+xxxxxxxxxxxxxxxxxxxx中y文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[23G,̲
+
+Overwriting a narrow character with a combining diacritic with an East Asian
+wide character, in the middle of an 80-column line with mixed narrow and
+widechars:
+
+xxxxxxxxàxxx[...]xxx中文xxx[...]xxx你好xESC [ 9 G 中:
+xxxxxxxxàxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[9G中
+
+xxxxxxxxàxxx[...]xxx中文xxx[...]xxx你好xESC [ 8 G 中:
+xxxxxxxxàxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[8G中
+
+Overwriting an East Asian wide character with combining diacritic with a narrow
+character without combining diacritic, in the middle of an 80-column line with
+mixed narrow and wide chars:
+
+xxx[...]xxx中文[combining low line]xxx[...]xxx你好ESC [ 23 G y:
+xxxxxxxxxxxxxxxxxxxx中文̲xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[23Gy
+
+xxx[...]xxx中文[combining low line]。xxx[...]xxx你好xESC [ 23 G y:
+xxxxxxxxxxxxxxxxxxxx中文̲。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[23Gy
+
diff --git a/tests/wideChars/last-column-wrapping.txt b/tests/wideChars/last-column-wrapping.txt
new file mode 100644
index 000000000..23ad10636
--- /dev/null
+++ b/tests/wideChars/last-column-wrapping.txt
@@ -0,0 +1,4 @@
+A line taking 81 screen columns, with an East Asian wide character at column 79
+(should be truncated or wrapped on an 80-column terminal):
+
+xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好
diff --git a/tests/wideChars/linkification.txt b/tests/wideChars/linkification.txt
new file mode 100644
index 000000000..dabd24a7c
--- /dev/null
+++ b/tests/wideChars/linkification.txt
@@ -0,0 +1,6 @@
+A link on a line with East Asian wide characters:
+中国国际航空公司 http://www.airchina.com/
+
+A link on a long line with East Asian wide characters (the link should be
+wrapped across two lines on an 80-column terminal):
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 中国国际航空公司 http://www.airchina.com/
diff --git a/tests/wideChars/overwriting1.txt b/tests/wideChars/overwriting1.txt
new file mode 100644
index 000000000..b1fdd115a
--- /dev/null
+++ b/tests/wideChars/overwriting1.txt
@@ -0,0 +1,16 @@
+Overwriting an East Asian wide character with another one:
+
+中国 ESC [ 2 D 文:
+中国[2D文
+
+xxx中文 ESC [ 2 D 国:
+xxx中文[2D国
+
+Overwriting an East Asian wide character with another one, in the middle of
+an 80-column line with mixed narrow and wide chars:
+
+xxx[...]xxx中文xxx[...]xxx你好ESC [ 23 G 国:
+xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[23G国
+
+xxx[...]xxx中文。xxx[...]xxx你好xESC [ 23 G 国:
+xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[23G国
diff --git a/tests/wideChars/overwriting2.txt b/tests/wideChars/overwriting2.txt
new file mode 100644
index 000000000..66d438286
--- /dev/null
+++ b/tests/wideChars/overwriting2.txt
@@ -0,0 +1,16 @@
+Inserting an East Asian wide character into the middle of another one:
+
+中国 ESC [ D 文:
+中国[D文
+
+xxx中文 ESC [ D 国:
+xxx中文[D国
+
+Inserting an East Asian wide character into the middle of another one, in the
+middle of an 80-column line with mixed narrow and wide chars:
+
+xxx[...]xxx中文xxx[...]xxx你好xESC [ 24 G 国:
+xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[24G国
+
+xxx[...]xxx中文。xxx[...]xxx你好ESC [ 24 G 国:
+xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[24G国
diff --git a/tests/wideChars/overwriting3.txt b/tests/wideChars/overwriting3.txt
new file mode 100644
index 000000000..93be2cc28
--- /dev/null
+++ b/tests/wideChars/overwriting3.txt
@@ -0,0 +1,16 @@
+Overwriting an East Asian wide character with a narrow character:
+
+中国 ESC [ 2 D x:
+中国[2Dx
+
+xxx中文 ESC [ 2 D x:
+xxx中文[2Dx
+
+Overwriting an East Asian wide character with a narrow character, in the middle
+of an 80-column line with mixed narrow and wide chars:
+
+xxx[...]xxx中文xxx[...]xxx你好xESC [ 23 G y:
+xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[23Gy
+
+xxx[...]xxx中文。xxx[...]xxx你好ESC [ 23 G y:
+xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[23Gy
diff --git a/tests/wideChars/overwriting4.txt b/tests/wideChars/overwriting4.txt
new file mode 100644
index 000000000..7c3aaa286
--- /dev/null
+++ b/tests/wideChars/overwriting4.txt
@@ -0,0 +1,16 @@
+Overwriting a narrow character with an East Asian wide character:
+
+xxx ESC [ D 中:
+xxx[D中
+
+xxx ESC [ 3 D 中:
+xxx[3D中
+
+Overwriting a narrow character with an East Asian wide character, in the middle
+of an 80-column line with mixed narrow and wide chars:
+
+xxx[...]xxx中文xxx[...]xxx你好xESC [ 25 G 版:
+xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[25G版
+
+xxx[...]xxx中文y。xxx[...]xxx你好ESC [ 25 G 版:
+xxxxxxxxxxxxxxxxxxxx中文y。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[25G版
diff --git a/tests/wideChars/overwriting5.txt b/tests/wideChars/overwriting5.txt
new file mode 100644
index 000000000..670a9c42d
--- /dev/null
+++ b/tests/wideChars/overwriting5.txt
@@ -0,0 +1,16 @@
+Inserting a narrow character into the middle of an East Asian wide character:
+
+中国 ESC [ D x:
+中国[Dx
+
+xxx中文 ESC [ D x:
+xxx中文[Dx
+
+Inserting a narrow character into the middle of an East Asian wide character,
+in the middle of an 80-column line with mixed narrow and wide chars:
+
+xxx[...]xxx中文xxx[...]xxx你好xESC [ 24 G y:
+xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[24Gy
+
+xxx[...]xxx中文。xxx[...]xxx你好ESC [ 24 G y:
+xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[24Gy
diff --git a/tests/wideChars/sip-chars.txt b/tests/wideChars/sip-chars.txt
new file mode 100644
index 000000000..ee39100a0
--- /dev/null
+++ b/tests/wideChars/sip-chars.txt
@@ -0,0 +1,100 @@
+East Asian wide characters in the Supplementary Ideographic Plane (thanks to
+http://www.i18nguy.com/unicode/supplementary-test.html):
+
+𠜎𠜱𠝹𠱓𠱸
+
+Mixed East Asian wide characters from the BMP and SIP:
+
+中文:𠜎𠜱,你好,𠝹国。
+𠱸,中文:你好,𠝹国。
+
+Narrow characters and mixed east Asian wide characters from the BMP and SIP, in
+an 80-column line:
+xxxxxxxxxx中文:𠜎𠜱,你好,xx𠝹国。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好xxxxxxxxxx
+xxxxxxxxxx𠱸,中文:你xx好,𠝹国。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx好
+
+Overwriting an East Asian wide character from the SIP with a narrow character:
+
+中文:𠜎 ESC [ 2 D x:
+中文:𠜎[2Dx
+
+xxx中文:𠜎 ESC [ 2 D x:
+xxx中文:𠜎[2Dx
+
+Overwriting an East Asian wide character from the SIP with a narrow character,
+in the middle of an 80-column line with mixed narrow and wide chars:
+
+xxx[...]xxx中文:𠜎xxx[...]xxx你好xESC [ 27 G y:
+xxxxxxxxxxxxxxxxxxxx中文:𠜎xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[27Gy
+
+xxx[...]xxx中文:𠜎。xxx[...]xxx你好ESC [ 27 G y:
+xxxxxxxxxxxxxxxxxxxx中文:𠜎。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[27Gy
+
+Overwriting an East Asian wide character from the SIP with an East Asian wide
+character from the BMP:
+
+中文:𠜎 ESC [ 2 D 国:
+中文:𠜎[2D国
+
+xxx中文:𠜎 ESC [ 2 D 国:
+xxx中文:𠜎[2D国
+
+Overwriting an East Asian wide character from the SIP with an East Asian wide
+character from the BMP in the middle of an 80-column line with mixed narrow and
+wide chars:
+
+xxx[...]xxx中文:𠜎xxx[...]xxx你好xESC [ 27 G 国:
+xxxxxxxxxxxxxxxxxxxx中文:𠜎xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[27G国
+
+xxx[...]xxx中文:𠜎。xxx[...]xxx你好ESC [ 27 G 国:
+xxxxxxxxxxxxxxxxxxxx中文:𠜎。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[27G国
+
+Overwriting an East Asian wide character from the BMP with an East Asian wide
+character from the SIP:
+
+中文:国 ESC [ 2 D 𠜎:
+中文:国[2D𠜎
+
+xxx中文:国 ESC [ 2 D 𠜎:
+xxx中文:国[2D𠜎
+
+Overwriting an East Asian wide character from the BMP with an East Asian wide
+character from the SIP in the middle of an 80-column line with mixed narrow and
+wide chars:
+
+xxx[...]xxx中文:国xxx[...]xxx你好xESC [ 27 G 𠜎:
+xxxxxxxxxxxxxxxxxxxx中文:国xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[27G𠜎
+
+xxx[...]xxx中文:国。xxx[...]xxx你好ESC [ 27 G 𠜎:
+xxxxxxxxxxxxxxxxxxxx中文:国。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[27G𠜎
+
+Inserting a narrow character into the middle of an East Asian wide character
+from the SIP in the middle of an 80-column line with mixed narrow and wide
+chars:
+
+xxx[...]xxx中文:𠜎xxx[...]xxx你好xESC [ 28 G y:
+xxxxxxxxxxxxxxxxxxxx中文:𠜎xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[28Gy
+
+xxx[...]xxx中文:𠜎。xxx[...]xxx你好ESC [ 28 G y:
+xxxxxxxxxxxxxxxxxxxx中文:𠜎。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[28Gy
+
+Inserting an East Asian wide character from the SIP into the middle of an East
+Asian wide character from the BMP in the middle of an 80-column line with mixed
+narrow and wide chars:
+
+xxx[...]xxx中文:国xxx[...]xxx你好xESC [ 28 G 𠜎:
+xxxxxxxxxxxxxxxxxxxx中文:国xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[28G𠜎
+
+xxx[...]xxx中文:国。xxx[...]xxx你好ESC [ 28 G 𠜎:
+xxxxxxxxxxxxxxxxxxxx中文:国。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[28G𠜎
+
+Inserting an East Asian wide character from the BMP into the middle of an East
+Asian wide character from the SIP in the middle of an 80-column line with mixed
+narrow and wide chars:
+
+xxx[...]xxx中文:𠜎xxx[...]xxx你好xESC [ 28 G 国:
+xxxxxxxxxxxxxxxxxxxx中文:𠜎xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x[28G国
+
+xxx[...]xxx中文:𠜎。xxx[...]xxx你好ESC [ 28 G 国:
+xxxxxxxxxxxxxxxxxxxx中文:𠜎。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好[28G国
+
diff --git a/tools/build-debug b/tools/build-debug
new file mode 100755
index 000000000..a56fb0544
--- /dev/null
+++ b/tools/build-debug
@@ -0,0 +1,15 @@
+#!/bin/bash
+# command line build script for building debug version
+set -e
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+ATE_ROOT="$( cd $DIR/.. && pwd )"
+
+cd "$ATE_ROOT"
+
+if [ ! -f "local.properties" ]; then
+ echo "local.properties does not exist. Please create it."
+ exit 1
+fi
+
+./gradlew assembleDebug
diff --git a/tools/build-release b/tools/build-release
new file mode 100755
index 000000000..de41d2eca
--- /dev/null
+++ b/tools/build-release
@@ -0,0 +1,18 @@
+#!/bin/bash
+# command line build script for building release version
+# This will only work on my (Jack Palevich) computer, because it
+# requires the private key store used to sign the release version
+# of Terminal Emulator for Android.
+set -e
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+ATE_ROOT="$( cd $DIR/.. && pwd )"
+
+cd "$ATE_ROOT"
+
+if [ ! -f "local.properties" ]; then
+ echo "local.properties does not exist. Please create it."
+ exit 1
+fi
+
+./gradlew assembleRelease && ./tools/sign-release-build
diff --git a/tools/import-icons b/tools/import-icons
new file mode 100755
index 000000000..b92e84a75
--- /dev/null
+++ b/tools/import-icons
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -e
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+ATE_ROOT="$( cd $DIR/.. && pwd )"
+
+cd "$ATE_ROOT"
+
+if [ "$#" -ne 1 ] || ! [ -f "$1-512.png" ]; then
+ echo "Usage: $0 BASE_PATH_OF_ICONS_TO_IMPORT" >&2
+ exit 1
+fi
+
+ICON=$1
+
+RES=term/src/main/res
+IC_LAUNCHER=ic_launcher.png
+
+cp $ICON-48.png $RES/drawable/$IC_LAUNCHER
+cp $ICON-36.png $RES/drawable-ldpi/$IC_LAUNCHER
+cp $ICON-48.png $RES/drawable-mdpi/$IC_LAUNCHER
+cp $ICON-72.png $RES/drawable-hdpi/$IC_LAUNCHER
+cp $ICON-96.png $RES/drawable-xhdpi/$IC_LAUNCHER
+cp $ICON-144.png $RES/drawable-xxhdpi/$IC_LAUNCHER
+cp $ICON-192.png $RES/drawable-xxxhdpi/$IC_LAUNCHER
+
+cp $ICON-512.png artwork/android-terminal-emulator-512.png
+
+echo "Remember to update artwork/Feature Graphic.xcf"
diff --git a/tools/increment-version-number b/tools/increment-version-number
new file mode 100755
index 000000000..23142a1e0
--- /dev/null
+++ b/tools/increment-version-number
@@ -0,0 +1,20 @@
+#!/bin/bash
+# command line build script for building release version
+# This will only work on my (Jack Palevich) computer, because it
+# requires the private key store used to sign the release version
+# of Terminal Emulator for Android.
+set -e
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+ATE_ROOT="$( cd $DIR/.. && pwd )"
+
+cd "$ATE_ROOT"
+
+MANIFEST="./term/src/main/AndroidManifest.xml"
+
+if [ ! -f "$MANIFEST" ]; then
+ echo "manifest $MANIFEST not found."
+ exit 1
+fi
+
+perl -i -pe 's/(?<=android:versionName="1.0.)(\d+)/$1+1/e;s/(?<=android:versionCode=")(\d+)/$1+1/e;' "$MANIFEST"
diff --git a/tools/install-sdk-packages b/tools/install-sdk-packages
new file mode 100755
index 000000000..bae53259b
--- /dev/null
+++ b/tools/install-sdk-packages
@@ -0,0 +1,23 @@
+#!/bin/bash
+# command line build script for installing required SDK packages.
+set -e
+
+if [ -z "${ANDROID_SDK_ROOT+xxx}" ]; then
+ echo "Please define ANDROID_SDK_ROOT to point to the Android SDK"
+ exit 1
+fi
+
+if [ ! -d "$ANDROID_SDK_ROOT" ]; then
+ echo "The directory $ANDROID_SDK_ROOT = ${ANDROID_SDK_ROOT} does not exist."
+ exit 1
+fi
+
+ANDROID="$ANDROID_SDK_ROOT/tools/android"
+
+# The available SDK versions can be listed by:
+# $ANDROID list sdk --all -e
+
+# The update sdk command takes a comma-separated list of packages to install.
+# Installing them all at once may simplify the process of agreeing to licenses.
+
+$ANDROID update sdk -u -a -t android-22,build-tools-22.0.1,extra-android-m2repository
diff --git a/tools/push-and-run-debug b/tools/push-and-run-debug
new file mode 100755
index 000000000..061730811
--- /dev/null
+++ b/tools/push-and-run-debug
@@ -0,0 +1,18 @@
+#!/bin/bash
+# command line build script for installing and running terminal emulator.
+set -e
+
+if [ -z "${ANDROID_SDK_ROOT+xxx}" ]; then
+ echo "Please define ANDROID_SDK_ROOT to point to the Android SDK"
+ exit 1
+fi
+
+if [ ! -d "$ANDROID_SDK_ROOT" ]; then
+ echo "The directory $ANDROID_SDK_ROOT = ${ANDROID_SDK_ROOT} does not exist."
+ exit 1
+fi
+
+ADB="$ANDROID_SDK_ROOT/platform-tools/adb"
+
+$ADB uninstall jackpal.androidterm
+$ADB install -r term/build/outputs/apk/term-debug.apk && $ADB shell am start -n jackpal.androidterm/jackpal.androidterm.Term
diff --git a/tools/push-and-run-release b/tools/push-and-run-release
new file mode 100755
index 000000000..d4e087cca
--- /dev/null
+++ b/tools/push-and-run-release
@@ -0,0 +1,18 @@
+#!/bin/bash
+# command line build script for installing and running terminal emulator.
+set -e
+
+if [ -z "${ANDROID_SDK_ROOT+xxx}" ]; then
+ echo "Please define ANDROID_SDK_ROOT to point to the Android SDK"
+ exit 1
+fi
+
+if [ ! -d "$ANDROID_SDK_ROOT" ]; then
+ echo "The directory $ANDROID_SDK_ROOT = ${ANDROID_SDK_ROOT} does not exist."
+ exit 1
+fi
+
+ADB="$ANDROID_SDK_ROOT/platform-tools/adb"
+
+$ADB uninstall jackpal.androidterm
+$ADB install -r term/build/outputs/apk/Term.apk && $ADB shell am start -n jackpal.androidterm/jackpal.androidterm.Term
diff --git a/tools/push-samples-debug b/tools/push-samples-debug
new file mode 100755
index 000000000..6f94d1a4a
--- /dev/null
+++ b/tools/push-samples-debug
@@ -0,0 +1,24 @@
+#!/bin/bash
+# command line build script for installing and running terminal emulator.
+set -e
+
+if [ -z "${ANDROID_SDK_ROOT+xxx}" ]; then
+ echo "Please define ANDROID_SDK_ROOT to point to the Android SDK"
+ exit 1
+fi
+
+if [ ! -d "$ANDROID_SDK_ROOT" ]; then
+ echo "The directory $ANDROID_SDK_ROOT = ${ANDROID_SDK_ROOT} does not exist."
+ exit 1
+fi
+
+ADB="$ANDROID_SDK_ROOT/platform-tools/adb"
+
+$ADB uninstall jackpal.androidterm.sample.intents
+$ADB install -r samples/intents/build/outputs/apk/intents-debug.apk
+
+$ADB uninstall jackpal.androidterm.sample.pathbroadcasts
+$ADB install -r samples/pathbroadcasts/build/outputs/apk/pathbroadcasts-debug.apk
+
+$ADB uninstall jackpal.androidterm.sample.telnet
+$ADB install -r samples/telnet/build/outputs/apk/telnet-debug.apk
diff --git a/tools/sign-release-build b/tools/sign-release-build
new file mode 100755
index 000000000..9421ac4d3
--- /dev/null
+++ b/tools/sign-release-build
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+# Script to sign the APK. Only needed by Jack Palevich.
+
+# command line build script for building release version
+# This will only work on my (Jack Palevich) computer, because it
+# requires the private key store used to sign the release version
+# of Terminal Emulator for Android.
+set -e
+
+ANDROID_TERMINAL_EMULATOR_KEYSTORE="$HOME/Documents/workspace/keystore/jackpal.keystore"
+
+ZIPALIGN="/Users/jack/Library/Android/sdk/build-tools/21.1.2/zipalign"
+
+if [ ! -f "$ANDROID_TERMINAL_EMULATOR_KEYSTORE" ]; then
+ echo "Release keystore file is missing at ${ANDROID_TERMINAL_EMULATOR_KEYSTORE}. Cannot build release build."
+ exit 1
+fi
+
+if [ ! -f "$ZIPALIGN" ]; then
+ echo "Zipalign tool is missing at ${ZIPALIGN}. Cannot build release build."
+ exit 1
+fi
+
+
+DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+
+APKDIR="$DIR/../term/build/outputs/apk"
+
+if [ ! -d "$APKDIR" ]; then
+ echo "No APK directory $APKDIR"
+ exit 1
+fi
+
+cd "$APKDIR"
+
+jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 \
+ -keystore "$ANDROID_TERMINAL_EMULATOR_KEYSTORE" \
+ -signedjar term-release-signed.apk \
+ term-release-unsigned.apk jackpal.keystore
+
+# Verify signature
+# jarsigner -verify -verbose -certs term-release-signed.apk
+
+# Align for faster loading.
+
+rm -f Term.apk
+"$ZIPALIGN" -v 4 term-release-signed.apk Term.apk
+
+echo "Signed-and-aligned APK:"
+echo `pwd`/Term.apk
+