Project: Refactor Terminal Client

This commit is contained in:
zt515 2017-07-21 00:42:30 +08:00
parent e21d7ccf6f
commit bdc8f60da0
21 changed files with 441 additions and 253 deletions

View File

@ -17,8 +17,8 @@ android {
applicationId "io.neoterm" applicationId "io.neoterm"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 25 targetSdkVersion 25
versionCode 13 versionCode 14
versionName "1.2.0-rc2" versionName "1.2.0-rc3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
resConfigs "zh" resConfigs "zh"
externalNativeBuild { externalNativeBuild {

View File

@ -28,7 +28,7 @@ import java.util.UUID;
* <p> * <p>
* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks! * NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks!
*/ */
public final class TerminalSession extends TerminalOutput { public class TerminalSession extends TerminalOutput {
/** Callback to be invoked when a {@link TerminalSession} changes. */ /** Callback to be invoked when a {@link TerminalSession} changes. */
public interface SessionChangedCallback { public interface SessionChangedCallback {
@ -143,18 +143,16 @@ public final class TerminalSession extends TerminalOutput {
private final String mShellPath; private final String mShellPath;
private final String mCwd; private final String mCwd;
private final String mInitialCommand;
private final String[] mArgs; private final String[] mArgs;
private final String[] mEnv; private final String[] mEnv;
public TerminalSession(String shellPath, String cwd, String initialCommand, String[] args, String[] env, SessionChangedCallback changeCallback) { public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) {
mChangeCallback = changeCallback; mChangeCallback = changeCallback;
this.mShellPath = shellPath; this.mShellPath = shellPath;
this.mCwd = cwd; this.mCwd = cwd;
this.mArgs = args; this.mArgs = args;
this.mEnv = env; this.mEnv = env;
this.mInitialCommand = initialCommand;
} }
/** Inform the attached pty of the new size and reflow or initialize the emulator. */ /** Inform the attached pty of the new size and reflow or initialize the emulator. */
@ -227,10 +225,6 @@ public final class TerminalSession extends TerminalOutput {
mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode)); mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode));
} }
}.start(); }.start();
if (mInitialCommand != null && mInitialCommand.length() > 0) {
write(mInitialCommand + '\r');
}
} }
/** Write data to the shell process. */ /** Write data to the shell process. */

View File

@ -96,67 +96,6 @@ object NeoPreference {
return null return null
} }
fun buildEnvironment(cwd: String?, systemShell: Boolean, executablePath: String): Array<String> {
var cwd = cwd
File(NeoTermPath.HOME_PATH).mkdirs()
if (cwd == null) cwd = NeoTermPath.HOME_PATH
val termEnv = "TERM=xterm-256color"
val homeEnv = "HOME=" + NeoTermPath.HOME_PATH
val androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT")
val androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA")
val externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE")
if (systemShell) {
val pathEnv = "PATH=" + System.getenv("PATH")
return arrayOf(termEnv, homeEnv, androidRootEnv, androidDataEnv, externalStorageEnv, pathEnv)
} else {
val ps1Env = "PS1=$ "
val langEnv = "LANG=en_US.UTF-8"
val pathEnv = "PATH=" + buildPathEnv()
val ldEnv = "LD_LIBRARY_PATH=" + buildLdLibraryEnv()
val pwdEnv = "PWD=" + cwd
val tmpdirEnv = "TMPDIR=${NeoTermPath.USR_PATH}/tmp"
return arrayOf(termEnv, homeEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv, tmpdirEnv)
}
}
private fun buildLdLibraryEnv(): String {
val builder = StringBuilder("${NeoTermPath.USR_PATH}/lib")
val programSelection = NeoPreference.loadString(R.string.key_general_program_selection, VALUE_NEOTERM_ONLY)
val systemPath = System.getenv("LD_LIBRARY_PATH")
if (programSelection != VALUE_NEOTERM_ONLY) {
builder.append(":$systemPath")
}
return builder.toString()
}
private fun buildPathEnv(): String {
val builder = StringBuilder()
val programSelection = NeoPreference.loadString(R.string.key_general_program_selection, VALUE_NEOTERM_ONLY)
val basePath = "${NeoTermPath.USR_PATH}/bin:${NeoTermPath.USR_PATH}/bin/applets"
val systemPath = System.getenv("PATH")
when (programSelection) {
VALUE_NEOTERM_ONLY -> {
builder.append(basePath)
}
VALUE_NEOTERM_FIRST -> {
builder.append("$basePath:$systemPath")
}
VALUE_SYSTEM_FIRST -> {
builder.append("$systemPath:$basePath")
}
}
return builder.toString()
}
/** /**
* TODO * TODO
* To print the job name about to be executed in bash: * To print the job name about to be executed in bash:

View File

@ -76,8 +76,11 @@ class NeoTermService : Service() {
val sessions: List<TerminalSession> val sessions: List<TerminalSession>
get() = mTerminalSessions get() = mTerminalSessions
fun createTermSession(executablePath: String?, arguments: Array<String>?, cwd: String?, initialCommand: String?, env: Array<String>?, sessionCallback: TerminalSession.SessionChangedCallback?, systemShell: Boolean): TerminalSession { fun createTermSession(executablePath: String?, arguments: Array<String>?,
val session = TerminalUtils.createSession(this, executablePath, arguments, cwd, initialCommand, env, sessionCallback, systemShell) cwd: String?, initialCommand: String?,
env: Array<Pair<String, String>>?, sessionCallback:
TerminalSession.SessionChangedCallback?, systemShell: Boolean): TerminalSession {
val session = TerminalUtils.createShellSession(this, executablePath, arguments, cwd, initialCommand, env, sessionCallback, systemShell)
mTerminalSessions.add(session) mTerminalSessions.add(session)
updateNotification() updateNotification()
return session return session

View File

@ -0,0 +1,199 @@
package io.neoterm.terminal
import android.content.Context
import android.widget.Toast
import io.neoterm.R
import io.neoterm.backend.TerminalSession
import io.neoterm.preference.NeoPreference
import io.neoterm.preference.NeoTermPath
import io.neoterm.terminal.client.TermSessionCallback
import java.io.File
/**
* @author kiva
*/
open class ShellTermSession private constructor(shellPath: String, cwd: String, args: Array<String>, env: Array<String>, changeCallback: SessionChangedCallback) : TerminalSession(shellPath, cwd, args, env, changeCallback) {
var initialCommand : String? = null
override fun initializeEmulator(columns: Int, rows: Int) {
super.initializeEmulator(columns, rows)
val initCommand = initialCommand
if (initCommand != null && initCommand.isNotEmpty()) {
write(initCommand + '\r')
}
}
class Builder {
private var shell: String? = null
private var cwd: String? = null
private var args: MutableList<String>? = null
private var env: MutableList<Pair<String, String>>? = null
private var changeCallback: SessionChangedCallback? = null
private var systemShell = false
fun shell(shell: String?): Builder {
this.shell = shell
return this
}
fun currentWorkingDirectory(cwd: String?): Builder {
this.cwd = cwd
return this
}
fun arg(arg: String?): Builder {
if (arg != null) {
if (args == null) {
args = mutableListOf(arg)
} else {
args!!.add(arg)
}
} else {
this.args = null
}
return this
}
fun argArray(args: Array<String>?): Builder {
if (args != null) {
if (args.isEmpty()) {
this.args = null
return this
}
args.forEach { arg(it) }
} else {
this.args = null
}
return this
}
fun env(env: Pair<String, String>?): Builder {
if (env != null) {
if (this.env == null) {
this.env = mutableListOf(env)
} else {
this.env!!.add(env)
}
} else {
this.env = null
}
return this
}
fun envArray(env: Array<Pair<String, String>>?): Builder {
if (env != null) {
if (env.isEmpty()) {
this.env = null
return this
}
env.forEach { env(it) }
} else {
this.env = null
}
return this
}
fun callback(callback: SessionChangedCallback?): Builder {
this.changeCallback = callback
return this
}
fun systemShell(systemShell: Boolean): Builder {
this.systemShell = systemShell
return this
}
fun create(context: Context): ShellTermSession {
val cwd = this.cwd ?: NeoTermPath.HOME_PATH
var shell = this.shell ?:
if (systemShell)
"/system/bin/sh"
else
NeoTermPath.USR_PATH + "/bin/" + NeoPreference.loadString(R.string.key_general_shell, "sh")
if (!File(shell).exists()) {
Toast.makeText(context, context.getString(R.string.shell_not_found, shell), Toast.LENGTH_LONG).show()
shell = NeoTermPath.USR_PATH + "/bin/sh"
}
val args = this.args ?: mutableListOf(shell)
val env = transformEnvironment(this.env) ?: buildEnvironment(cwd, systemShell, shell)
val callback = changeCallback ?: TermSessionCallback()
return ShellTermSession(shell, cwd, args.toTypedArray(), env, callback)
}
private fun transformEnvironment(env: MutableList<Pair<String, String>>?): Array<String>? {
if (env == null) {
return null
}
val result = mutableListOf<String>()
env.mapTo(result, { "${it.first}=${it.second}" })
return result.toTypedArray()
}
private fun buildEnvironment(cwd: String?, systemShell: Boolean, executablePath: String): Array<String> {
var cwd = cwd
File(NeoTermPath.HOME_PATH).mkdirs()
if (cwd == null) cwd = NeoTermPath.HOME_PATH
val termEnv = "TERM=xterm-256color"
val homeEnv = "HOME=" + NeoTermPath.HOME_PATH
val androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT")
val androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA")
val externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE")
if (systemShell) {
val pathEnv = "PATH=" + System.getenv("PATH")
return arrayOf(termEnv, homeEnv, androidRootEnv, androidDataEnv, externalStorageEnv, pathEnv)
} else {
val ps1Env = "PS1=$ "
val langEnv = "LANG=en_US.UTF-8"
val pathEnv = "PATH=" + buildPathEnv()
val ldEnv = "LD_LIBRARY_PATH=" + buildLdLibraryEnv()
val pwdEnv = "PWD=" + cwd
val tmpdirEnv = "TMPDIR=${NeoTermPath.USR_PATH}/tmp"
return arrayOf(termEnv, homeEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv, tmpdirEnv)
}
}
private fun buildLdLibraryEnv(): String {
val builder = StringBuilder("${NeoTermPath.USR_PATH}/lib")
val programSelection = NeoPreference.loadString(R.string.key_general_program_selection, NeoPreference.VALUE_NEOTERM_ONLY)
val systemPath = System.getenv("LD_LIBRARY_PATH")
if (programSelection != NeoPreference.VALUE_NEOTERM_ONLY) {
builder.append(":$systemPath")
}
return builder.toString()
}
private fun buildPathEnv(): String {
val builder = StringBuilder()
val programSelection = NeoPreference.loadString(R.string.key_general_program_selection, NeoPreference.VALUE_NEOTERM_ONLY)
val basePath = "${NeoTermPath.USR_PATH}/bin:${NeoTermPath.USR_PATH}/bin/applets"
val systemPath = System.getenv("PATH")
when (programSelection) {
NeoPreference.VALUE_NEOTERM_ONLY -> {
builder.append(basePath)
}
NeoPreference.VALUE_NEOTERM_FIRST -> {
builder.append("$basePath:$systemPath")
}
NeoPreference.VALUE_SYSTEM_FIRST -> {
builder.append("$systemPath:$basePath")
}
}
return builder.toString()
}
}
}

View File

@ -1,4 +1,4 @@
package io.neoterm.ui.term.tab package io.neoterm.terminal.client
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent

View File

@ -0,0 +1,47 @@
package io.neoterm.terminal.client
import io.neoterm.backend.TerminalSession
import io.neoterm.view.ExtraKeysView
import io.neoterm.view.OnAutoCompleteListener
import io.neoterm.view.TerminalView
/**
* @author kiva
*/
class TermDataHolder {
var termSession: TerminalSession? = null
var sessionCallback: TermSessionCallback? = null
var viewClient: TermViewClient? = null
var onAutoCompleteListener: OnAutoCompleteListener? = null
var termUI: TermUiPresenter? = null
var termView: TerminalView? = null
var extraKeysView: ExtraKeysView? = null
fun cleanup() {
onAutoCompleteListener?.onCleanUp()
onAutoCompleteListener = null
sessionCallback?.termData = null
viewClient?.termData = null
termUI = null
termView = null
extraKeysView = null
termSession = null
}
fun initializeSessionWith(session: TerminalSession, sessionCallback: TermSessionCallback?, viewClient: TermViewClient?) {
this.termSession = session
this.sessionCallback = sessionCallback
this.viewClient = viewClient
sessionCallback?.termData = this
viewClient?.termData = this
}
fun initializeViewWith(termUI: TermUiPresenter?, termView: TerminalView?, eks: ExtraKeysView?) {
this.termUI = termUI
this.termView = termView
this.extraKeysView = eks
}
}

View File

@ -1,4 +1,4 @@
package io.neoterm.ui.term.tab package io.neoterm.terminal.client
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
@ -8,43 +8,40 @@ import android.os.Vibrator
import io.neoterm.R import io.neoterm.R
import io.neoterm.backend.TerminalSession import io.neoterm.backend.TerminalSession
import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoPreference
import io.neoterm.view.TerminalView
/** /**
* @author kiva * @author kiva
*/ */
class TermSessionCallback : TerminalSession.SessionChangedCallback { class TermSessionCallback : TerminalSession.SessionChangedCallback {
var termView: TerminalView? = null var termData: TermDataHolder? = null
var termTab: TermTab? = null
var bellId: Int = 0 var bellId: Int = 0
var soundPool: SoundPool? = null var soundPool: SoundPool? = null
override fun onTextChanged(changedSession: TerminalSession?) { override fun onTextChanged(changedSession: TerminalSession?) {
termView?.onScreenUpdated() termData?.termView?.onScreenUpdated()
} }
override fun onTitleChanged(changedSession: TerminalSession?) { override fun onTitleChanged(changedSession: TerminalSession?) {
if (changedSession?.title != null) { if (changedSession?.title != null) {
termTab?.updateTitle(changedSession.title) termData?.termUI?.requireUpdateTitle(changedSession.title)
} }
} }
override fun onSessionFinished(finishedSession: TerminalSession?) { override fun onSessionFinished(finishedSession: TerminalSession?) {
termTab?.onSessionFinished() termData?.termUI?.requireOnSessionFinished()
} }
override fun onClipboardText(session: TerminalSession?, text: String?) { override fun onClipboardText(session: TerminalSession?, text: String?) {
val termView = termData?.termView
if (termView != null) { if (termView != null) {
val clipboard = termView!!.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = termView.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.primaryClip = ClipData.newPlainText("", text) clipboard.primaryClip = ClipData.newPlainText("", text)
} }
} }
override fun onBell(session: TerminalSession?) { override fun onBell(session: TerminalSession?) {
if (termView == null) { val termView = termData?.termView ?: return
return
}
if (NeoPreference.loadBoolean(R.string.key_general_bell, false)) { if (NeoPreference.loadBoolean(R.string.key_general_bell, false)) {
if (soundPool == null) { if (soundPool == null) {
@ -61,8 +58,9 @@ class TermSessionCallback : TerminalSession.SessionChangedCallback {
} }
override fun onColorsChanged(session: TerminalSession?) { override fun onColorsChanged(session: TerminalSession?) {
if (session != null) { val termView = termData?.termView
termView?.onScreenUpdated() if (session != null && termView != null) {
termView.onScreenUpdated()
} }
} }
} }

View File

@ -0,0 +1,12 @@
package io.neoterm.terminal.client
/**
* @author kiva
*/
interface TermUiPresenter {
fun requireClose()
fun requireToggleFullScreen()
fun requirePaste()
fun requireUpdateTitle(title: String?)
fun requireOnSessionFinished()
}

View File

@ -1,4 +1,4 @@
package io.neoterm.ui.term.tab package io.neoterm.terminal.client
import android.content.Context import android.content.Context
import android.media.AudioManager import android.media.AudioManager
@ -11,8 +11,6 @@ import io.neoterm.backend.KeyHandler
import io.neoterm.backend.TerminalSession import io.neoterm.backend.TerminalSession
import io.neoterm.customize.eks.EksKeysManager import io.neoterm.customize.eks.EksKeysManager
import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoPreference
import io.neoterm.view.ExtraKeysView
import io.neoterm.view.TerminalView
import io.neoterm.view.TerminalViewClient import io.neoterm.view.TerminalViewClient
@ -26,9 +24,7 @@ class TermViewClient(val context: Context) : TerminalViewClient {
var sessionFinished: Boolean = false var sessionFinished: Boolean = false
var termTab: TermTab? = null var termData: TermDataHolder? = null
var termView: TerminalView? = null
var extraKeysView: ExtraKeysView? = null
override fun onScale(scale: Float): Float { override fun onScale(scale: Float): Float {
if (scale < 0.9f || scale > 1.1f) { if (scale < 0.9f || scale > 1.1f) {
@ -40,8 +36,11 @@ class TermViewClient(val context: Context) : TerminalViewClient {
} }
override fun onSingleTapUp(e: MotionEvent?) { override fun onSingleTapUp(e: MotionEvent?) {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) val termView = termData?.termView
.showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT) if (termView != null) {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
.showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT)
}
} }
override fun shouldBackButtonBeMappedToEscape(): Boolean { override fun shouldBackButtonBeMappedToEscape(): Boolean {
@ -53,10 +52,12 @@ class TermViewClient(val context: Context) : TerminalViewClient {
} }
override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean { override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean {
val termUI = termData?.termUI
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_ENTER -> { KeyEvent.KEYCODE_ENTER -> {
if (e?.action == KeyEvent.ACTION_DOWN && sessionFinished) { if (e?.action == KeyEvent.ACTION_DOWN && sessionFinished) {
termTab?.requireCloseTab() termUI?.requireClose()
return true return true
} }
return false return false
@ -67,9 +68,9 @@ class TermViewClient(val context: Context) : TerminalViewClient {
val unicodeChar = e.getUnicodeChar(0).toChar() val unicodeChar = e.getUnicodeChar(0).toChar()
if (unicodeChar == 'f'/* full screen */) { if (unicodeChar == 'f'/* full screen */) {
termTab?.requireToggleFullScreen() termUI?.requireToggleFullScreen()
} else if (unicodeChar == 'v') { } else if (unicodeChar == 'v') {
termTab?.requirePaste() termUI?.requirePaste()
} else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON).toChar() == '+') { } else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON).toChar() == '+') {
// We also check for the shifted char here since shift may be required to produce '+', // We also check for the shifted char here since shift may be required to produce '+',
// see https://github.com/termux/termux-api/issues/2 // see https://github.com/termux/termux-api/issues/2
@ -86,11 +87,13 @@ class TermViewClient(val context: Context) : TerminalViewClient {
} }
override fun readControlKey(): Boolean { override fun readControlKey(): Boolean {
return (extraKeysView != null && extraKeysView!!.readControlButton()) || mVirtualControlKeyDown val extraKeysView = termData?.extraKeysView
return (extraKeysView != null && extraKeysView.readControlButton()) || mVirtualControlKeyDown
} }
override fun readAltKey(): Boolean { override fun readAltKey(): Boolean {
return (extraKeysView != null && extraKeysView!!.readAltButton()) || mVirtualFnKeyDown val extraKeysView = termData?.extraKeysView
return (extraKeysView != null && extraKeysView.readAltButton()) || mVirtualFnKeyDown
} }
override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean { override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean {
@ -179,6 +182,8 @@ class TermViewClient(val context: Context) : TerminalViewClient {
} }
fun updateSuggestions(title: String?, force: Boolean = false) { fun updateSuggestions(title: String?, force: Boolean = false) {
val extraKeysView = termData?.extraKeysView
if (extraKeysView == null || title == null || title.isEmpty()) { if (extraKeysView == null || title == null || title.isEmpty()) {
return return
} }
@ -192,13 +197,17 @@ class TermViewClient(val context: Context) : TerminalViewClient {
} }
fun removeSuggestions() { fun removeSuggestions() {
val extraKeysView = termData?.extraKeysView
extraKeysView?.clearUserDefinedButton() extraKeysView?.clearUserDefinedButton()
} }
private fun changeFontSize(increase: Boolean) { private fun changeFontSize(increase: Boolean) {
val changedSize = (if (increase) 1 else -1) * 2 val termView = termData?.termView
val fontSize = termView!!.textSize + changedSize if (termView != null) {
termView!!.textSize = fontSize val changedSize = (if (increase) 1 else -1) * 2
NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize) val fontSize = termView.textSize + changedSize
termView.textSize = fontSize
NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize)
}
} }
} }

View File

@ -2,7 +2,6 @@ package io.neoterm.ui.customization
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
@ -57,7 +56,7 @@ class CustomizationActivity : AppCompatActivity() {
viewClient = BasicViewClient(terminalView) viewClient = BasicViewClient(terminalView)
sessionCallback = BasicSessionCallback(terminalView) sessionCallback = BasicSessionCallback(terminalView)
TerminalUtils.setupTerminalView(terminalView, viewClient) TerminalUtils.setupTerminalView(terminalView, viewClient)
session = TerminalUtils.createSession(this, "${NeoTermPath.USR_PATH}/bin/applets/echo", session = TerminalUtils.createShellSession(this, "${NeoTermPath.USR_PATH}/bin/applets/echo",
arrayOf("echo", "Hello NeoTerm."), null, null, null, sessionCallback, false) arrayOf("echo", "Hello NeoTerm."), null, null, null, sessionCallback, false)
terminalView.attachSession(session) terminalView.attachSession(session)

View File

@ -97,6 +97,7 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
dialog.setTitle(getString(R.string.done)) dialog.setTitle(getString(R.string.done))
} }
}) })
.imeEnabled(true)
.show("Installing $packageName") .show("Installing $packageName")
} }
} }
@ -185,6 +186,7 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
} }
}) })
.execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", "update")) .execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", "update"))
.imeEnabled(true)
.show("apt update") .show("apt update")
} }

View File

@ -6,7 +6,6 @@ import android.os.Bundle
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.Toast import android.widget.Toast
import com.igalata.bubblepicker.BubblePickerListener import com.igalata.bubblepicker.BubblePickerListener
@ -22,6 +21,7 @@ import io.neoterm.customize.setup.BaseFileInstaller
import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoPreference
import io.neoterm.preference.NeoTermPath import io.neoterm.preference.NeoTermPath
import io.neoterm.utils.PackageUtils import io.neoterm.utils.PackageUtils
import io.neoterm.utils.TerminalUtils
import io.neoterm.view.TerminalDialog import io.neoterm.view.TerminalDialog
import java.util.* import java.util.*
@ -38,7 +38,6 @@ class SetupActivity : AppCompatActivity() {
} }
lateinit var picker: BubblePicker lateinit var picker: BubblePicker
lateinit var nextButton: Button
lateinit var toast: Toast lateinit var toast: Toast
var aptUpdated = false var aptUpdated = false
@ -46,7 +45,7 @@ class SetupActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.ui_setup) setContentView(R.layout.ui_setup)
picker = findViewById(R.id.bubble_picker) as BubblePicker picker = findViewById(R.id.bubble_picker) as BubblePicker
nextButton = findViewById(R.id.setup_next) as Button val nextButton = findViewById(R.id.setup_next) as Button
nextButton.setOnClickListener { nextButton.setOnClickListener {
if (aptUpdated) { if (aptUpdated) {
val packageList = mutableListOf("apt", "install", "-y") val packageList = mutableListOf("apt", "install", "-y")
@ -101,7 +100,6 @@ class SetupActivity : AppCompatActivity() {
.setMessage(error.toString()) .setMessage(error.toString())
.setNegativeButton(R.string.use_system_shell, { _, _ -> .setNegativeButton(R.string.use_system_shell, { _, _ ->
setResult(Activity.RESULT_CANCELED) setResult(Activity.RESULT_CANCELED)
nextButton.visibility = View.VISIBLE
finish() finish()
}) })
.setPositiveButton(R.string.retry, { dialog, _ -> .setPositiveButton(R.string.retry, { dialog, _ ->
@ -114,21 +112,25 @@ class SetupActivity : AppCompatActivity() {
} }
private fun executeAptUpdate() { private fun executeAptUpdate() {
TerminalDialog(this) TerminalUtils.executeApt(this, "update", { exitStatus, dialog ->
.onFinish(object : TerminalDialog.SessionFinishedCallback { if (exitStatus == 0) {
override fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) { dialog.dismiss()
nextButton.visibility = View.VISIBLE aptUpdated = true
val exit = finishedSession?.exitStatus ?: 1 executeAptUpgrade()
if (exit == 0) { } else {
dialog.dismiss() dialog.setTitle(getString(R.string.error))
aptUpdated = true }
} else { })
dialog.setTitle(getString(R.string.error)) }
}
} private fun executeAptUpgrade() {
}) TerminalUtils.executeApt(this, "upgrade", { exitStatus, dialog ->
.execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", "update")) if (exitStatus == 0) {
.show("apt update") dialog.dismiss()
} else {
dialog.setTitle(getString(R.string.error))
}
})
} }
@SuppressLint("ShowToast") @SuppressLint("ShowToast")

View File

@ -33,10 +33,10 @@ import io.neoterm.ui.bonus.BonusActivity
import io.neoterm.ui.pm.PackageManagerActivity import io.neoterm.ui.pm.PackageManagerActivity
import io.neoterm.ui.settings.SettingActivity import io.neoterm.ui.settings.SettingActivity
import io.neoterm.ui.setup.SetupActivity import io.neoterm.ui.setup.SetupActivity
import io.neoterm.ui.term.tab.TermSessionCallback import io.neoterm.terminal.client.TermSessionCallback
import io.neoterm.ui.term.tab.TermTab import io.neoterm.ui.term.tab.TermTab
import io.neoterm.ui.term.tab.TermTabDecorator import io.neoterm.ui.term.tab.TermTabDecorator
import io.neoterm.ui.term.tab.TermViewClient import io.neoterm.terminal.client.TermViewClient
import io.neoterm.ui.term.tab.event.TabCloseEvent import io.neoterm.ui.term.tab.event.TabCloseEvent
import io.neoterm.ui.term.tab.event.TitleChangedEvent import io.neoterm.ui.term.tab.event.TitleChangedEvent
import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent
@ -189,8 +189,8 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
} }
override fun onSelectionChanged(tabSwitcher: TabSwitcher, selectedTabIndex: Int, selectedTab: Tab?) { override fun onSelectionChanged(tabSwitcher: TabSwitcher, selectedTabIndex: Int, selectedTab: Tab?) {
if (selectedTab is TermTab && selectedTab.termSession != null) { if (selectedTab is TermTab && selectedTab.termData.termSession != null) {
NeoPreference.storeCurrentSession(selectedTab.termSession!!) NeoPreference.storeCurrentSession(selectedTab.termData.termSession!!)
} }
} }
@ -199,8 +199,8 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
override fun onTabRemoved(tabSwitcher: TabSwitcher, index: Int, tab: Tab, animation: Animation) { override fun onTabRemoved(tabSwitcher: TabSwitcher, index: Int, tab: Tab, animation: Animation) {
if (tab is TermTab) { if (tab is TermTab) {
tab.termSession?.finishIfRunning() tab.termData.termSession?.finishIfRunning()
removeFinishedSession(tab.termSession) removeFinishedSession(tab.termData.termSession)
tab.cleanup() tab.cleanup()
} }
} }
@ -387,29 +387,32 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
val tabCount = tabSwitcher.count val tabCount = tabSwitcher.count
(0..(tabCount - 1)) (0..(tabCount - 1))
.map { tabSwitcher.getTab(it) } .map { tabSwitcher.getTab(it) }
.filter { it is TermTab && it.termSession == session } .filter { it is TermTab && it.termData.termSession == session }
.forEach { return } .forEach { return }
val sessionCallback = session.sessionChangedCallback as TermSessionCallback
val viewClient = TermViewClient(this)
val tab = createTab(session.title) as TermTab val tab = createTab(session.title) as TermTab
tab.sessionCallback = session.sessionChangedCallback as TermSessionCallback tab.termData.initializeSessionWith(session, sessionCallback, viewClient)
tab.viewClient = TermViewClient(this)
tab.termSession = session
addNewTab(tab, createRevealAnimation()) addNewTab(tab, createRevealAnimation())
switchToSession(tab) switchToSession(tab)
} }
private fun addNewSession(sessionName: String?, systemShell: Boolean, animation: Animation) { private fun addNewSession(sessionName: String?, systemShell: Boolean, animation: Animation) {
val tab = createTab(sessionName) as TermTab val sessionCallback = TermSessionCallback()
tab.sessionCallback = TermSessionCallback() val viewClient = TermViewClient(this)
tab.viewClient = TermViewClient(this) val session = termService!!.createTermSession(null, null,
tab.termSession = termService!!.createTermSession(null, null, null, null, null, sessionCallback, systemShell)
null, null, null, tab.sessionCallback, systemShell)
if (sessionName != null) { if (sessionName != null) {
tab.termSession!!.mSessionName = sessionName session.mSessionName = sessionName
} }
val tab = createTab(sessionName) as TermTab
tab.termData.initializeSessionWith(session, sessionCallback, viewClient)
addNewTab(tab, animation) addNewTab(tab, animation)
switchToSession(tab) switchToSession(tab)
} }
@ -421,7 +424,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
for (i in 0..tabSwitcher.count - 1) { for (i in 0..tabSwitcher.count - 1) {
val tab = tabSwitcher.getTab(i) val tab = tabSwitcher.getTab(i)
if (tab is TermTab && tab.termSession == session) { if (tab is TermTab && tab.termData.termSession == session) {
switchToSession(tab) switchToSession(tab)
break break
} }

View File

@ -16,7 +16,7 @@ import io.neoterm.customize.script.UserScript
import io.neoterm.customize.script.UserScriptManager import io.neoterm.customize.script.UserScriptManager
import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoPreference
import io.neoterm.services.NeoTermService import io.neoterm.services.NeoTermService
import io.neoterm.ui.term.tab.TermSessionCallback import io.neoterm.terminal.client.TermSessionCallback
import io.neoterm.utils.TerminalUtils import io.neoterm.utils.TerminalUtils
import java.io.File import java.io.File

View File

@ -5,58 +5,31 @@ import android.support.v7.widget.Toolbar
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import de.mrapp.android.tabswitcher.Tab import de.mrapp.android.tabswitcher.Tab
import io.neoterm.R import io.neoterm.R
import io.neoterm.backend.TerminalSession
import io.neoterm.customize.color.ColorSchemeManager import io.neoterm.customize.color.ColorSchemeManager
import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoPreference
import io.neoterm.terminal.client.TermDataHolder
import io.neoterm.terminal.client.TermUiPresenter
import io.neoterm.ui.term.tab.event.TabCloseEvent import io.neoterm.ui.term.tab.event.TabCloseEvent
import io.neoterm.ui.term.tab.event.TitleChangedEvent import io.neoterm.ui.term.tab.event.TitleChangedEvent
import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent
import io.neoterm.view.OnAutoCompleteListener
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
/** /**
* @author kiva * @author kiva
*/ */
class TermTab(title: CharSequence) : Tab(title) { class TermTab(title: CharSequence) : Tab(title), TermUiPresenter {
var termSession: TerminalSession? = null var termData = TermDataHolder()
var sessionCallback: TermSessionCallback? = null
var viewClient: TermViewClient? = null
var onAutoCompleteListener: OnAutoCompleteListener? = null
var toolbar: Toolbar? = null var toolbar: Toolbar? = null
fun updateColorScheme() { fun updateColorScheme() {
ColorSchemeManager.applyColorScheme(viewClient?.termView, viewClient?.extraKeysView, ColorSchemeManager.applyColorScheme(termData.termView, termData.extraKeysView,
ColorSchemeManager.getCurrentColorScheme()) ColorSchemeManager.getCurrentColorScheme())
} }
fun cleanup() { fun cleanup() {
onAutoCompleteListener?.onCleanUp() termData.cleanup()
onAutoCompleteListener = null
viewClient?.termTab = null
viewClient?.termView = null
viewClient?.extraKeysView = null
sessionCallback?.termView = null
sessionCallback?.termTab = null
toolbar = null toolbar = null
termSession = null
}
fun updateTitle(title: String) {
if (title.isNotEmpty()) {
this.title = title
EventBus.getDefault().post(TitleChangedEvent(title))
if (NeoPreference.loadBoolean(R.string.key_ui_suggestions, true)) {
viewClient?.updateSuggestions(title)
} else {
viewClient?.removeSuggestions()
}
}
}
fun onSessionFinished() {
viewClient?.sessionFinished = true
} }
fun onFullScreenModeChanged(fullScreen: Boolean) { fun onFullScreenModeChanged(fullScreen: Boolean) {
@ -64,13 +37,8 @@ class TermTab(title: CharSequence) : Tab(title) {
resetAutoCompleteStatus() resetAutoCompleteStatus()
} }
fun requireCloseTab() {
requireHideIme()
EventBus.getDefault().post(TabCloseEvent(this))
}
fun requireHideIme() { fun requireHideIme() {
val terminalView = viewClient?.termView val terminalView = termData.termView
if (terminalView != null) { if (terminalView != null) {
val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (imm.isActive) { if (imm.isActive) {
@ -79,16 +47,37 @@ class TermTab(title: CharSequence) : Tab(title) {
} }
} }
fun requireToggleFullScreen() { override fun requireToggleFullScreen() {
EventBus.getDefault().post(ToggleFullScreenEvent()) EventBus.getDefault().post(ToggleFullScreenEvent())
} }
fun requirePaste() { override fun requirePaste() {
viewClient?.termView?.pasteFromClipboard() termData.termView?.pasteFromClipboard()
}
override fun requireClose() {
requireHideIme()
EventBus.getDefault().post(TabCloseEvent(this))
}
override fun requireUpdateTitle(title: String?) {
if (title != null && title.isNotEmpty()) {
this.title = title
EventBus.getDefault().post(TitleChangedEvent(title))
if (NeoPreference.loadBoolean(R.string.key_ui_suggestions, true)) {
termData.viewClient?.updateSuggestions(title)
} else {
termData.viewClient?.removeSuggestions()
}
}
}
override fun requireOnSessionFinished() {
termData.viewClient?.sessionFinished = true
} }
fun resetAutoCompleteStatus() { fun resetAutoCompleteStatus() {
onAutoCompleteListener?.onCleanUp() termData.onAutoCompleteListener?.onCleanUp()
onAutoCompleteListener = null termData.onAutoCompleteListener = null
} }
} }

View File

@ -12,6 +12,7 @@ import io.neoterm.BuildConfig
import io.neoterm.R import io.neoterm.R
import io.neoterm.customize.color.ColorSchemeManager import io.neoterm.customize.color.ColorSchemeManager
import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoPreference
import io.neoterm.terminal.client.TermCompleteListener
import io.neoterm.ui.term.NeoTermActivity import io.neoterm.ui.term.NeoTermActivity
import io.neoterm.utils.TerminalUtils import io.neoterm.utils.TerminalUtils
import io.neoterm.view.ExtraKeysView import io.neoterm.view.ExtraKeysView
@ -58,32 +59,27 @@ class TermTabDecorator(val context: NeoTermActivity) : TabSwitcherDecorator() {
if (tab is TermTab) { if (tab is TermTab) {
val termTab = tab val termTab = tab
val termData = tab.termData
TerminalUtils.setupTerminalSession(termTab.termSession) TerminalUtils.setupTerminalSession(termData.termSession)
// 复用前一次的 TermSessionCallback // 复用前一次的 TermSessionCallback 和 TermViewClient
termTab.sessionCallback?.termView = view termData.initializeViewWith(termTab, view, extraKeysView)
termTab.sessionCallback?.termTab = termTab
// 复用上一次的 TermViewClient if (termData.termSession != null) {
termTab.viewClient?.termTab = termTab termData.viewClient?.updateSuggestions(termData.termSession?.title, true)
termTab.viewClient?.termView = view
termTab.viewClient?.extraKeysView = extraKeysView
if (termTab.termSession != null) {
termTab.viewClient?.updateSuggestions(termTab.termSession?.title, true)
} }
view.setTerminalViewClient(termTab.viewClient) view.setTerminalViewClient(termData.viewClient)
view.attachSession(termTab.termSession) view.attachSession(termData.termSession)
// Still in progress // Still in progress
// Only available for developers. // Only available for developers.
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
if (termTab.onAutoCompleteListener == null) { if (termData.onAutoCompleteListener == null) {
termTab.onAutoCompleteListener = createAutoCompleteListener(view) termData.onAutoCompleteListener = createAutoCompleteListener(view)
} }
view.onAutoCompleteListener = termTab.onAutoCompleteListener view.onAutoCompleteListener = termData.onAutoCompleteListener
} }
} }
} }

View File

@ -1,22 +1,22 @@
package io.neoterm.utils package io.neoterm.utils
import android.content.Context import android.content.Context
import android.widget.Toast
import io.neoterm.R import io.neoterm.R
import io.neoterm.backend.TerminalSession import io.neoterm.backend.TerminalSession
import io.neoterm.customize.font.FontManager import io.neoterm.customize.font.FontManager
import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoPreference
import io.neoterm.preference.NeoTermPath import io.neoterm.preference.NeoTermPath
import io.neoterm.view.BasicViewClient import io.neoterm.terminal.ShellTermSession
import io.neoterm.view.ExtraKeysView import io.neoterm.view.ExtraKeysView
import io.neoterm.view.TerminalDialog
import io.neoterm.view.TerminalView import io.neoterm.view.TerminalView
import java.io.File import io.neoterm.view.TerminalViewClient
/** /**
* @author kiva * @author kiva
*/ */
object TerminalUtils { object TerminalUtils {
fun setupTerminalView(terminalView: TerminalView?, terminalViewClient: BasicViewClient? = null) { fun setupTerminalView(terminalView: TerminalView?, terminalViewClient: TerminalViewClient? = null) {
terminalView?.textSize = NeoPreference.loadInt(NeoPreference.KEY_FONT_SIZE, 30) terminalView?.textSize = NeoPreference.loadInt(NeoPreference.KEY_FONT_SIZE, 30)
terminalView?.setTypeface(FontManager.getCurrentFont().getTypeFace()) terminalView?.setTypeface(FontManager.getCurrentFont().getTypeFace())
if (terminalViewClient != null) { if (terminalViewClient != null) {
@ -31,44 +31,23 @@ object TerminalUtils {
fun setupTerminalSession(session: TerminalSession?) { fun setupTerminalSession(session: TerminalSession?) {
} }
fun createSession(context: Context, executablePath: String?, arguments: Array<String>?, fun createShellSession(context: Context, executablePath: String?, arguments: Array<String>?,
cwd: String?, initialCommand: String?, env: Array<String>?, cwd: String?, initialCommand: String?, env: Array<Pair<String, String>>?,
sessionCallback: TerminalSession.SessionChangedCallback?, sessionCallback: TerminalSession.SessionChangedCallback?,
systemShell: Boolean): TerminalSession { systemShell: Boolean): TerminalSession {
val initCommand = initialCommand ?:
NeoPreference.loadString(R.string.key_general_initial_command, "")
var executablePath = executablePath val session = ShellTermSession.Builder()
var arguments = arguments .shell(executablePath)
var initialCommand = initialCommand .currentWorkingDirectory(cwd)
var cwd = cwd .callback(sessionCallback)
.systemShell(systemShell)
if (cwd == null) { .envArray(env)
cwd = NeoTermPath.HOME_PATH .argArray(arguments)
} .create(context)
if (executablePath == null) {
executablePath = if (systemShell)
"/system/bin/sh"
else
NeoTermPath.USR_PATH + "/bin/" + NeoPreference.loadString(R.string.key_general_shell, "sh")
if (!File(executablePath).exists()) {
Toast.makeText(context, context.getString(R.string.shell_not_found, executablePath), Toast.LENGTH_LONG).show()
executablePath = NeoTermPath.USR_PATH + "/bin/sh"
}
}
if (arguments == null) {
arguments = arrayOf<String>(executablePath)
}
if (initialCommand == null) {
initialCommand = NeoPreference.loadString(R.string.key_general_initial_command, "")
}
val session = TerminalSession(executablePath, cwd, initialCommand, arguments,
env ?: NeoPreference.buildEnvironment(cwd, systemShell, executablePath),
sessionCallback)
setupTerminalSession(session) setupTerminalSession(session)
session.initialCommand = initCommand
return session return session
} }
@ -91,4 +70,17 @@ object TerminalUtils {
builder.append('"') builder.append('"')
return builder.toString() return builder.toString()
} }
fun executeApt(context: Context, subCommand: String, callback: (Int, TerminalDialog) -> Unit) {
TerminalDialog(context)
.onFinish(object : TerminalDialog.SessionFinishedCallback {
override fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) {
val exit = finishedSession?.exitStatus ?: 1
callback(exit, dialog)
}
})
.imeEnabled(true)
.execute(NeoTermPath.APT_BIN_PATH, arrayOf("apt", subCommand))
.show("apt $subCommand")
}
} }

View File

@ -16,13 +16,12 @@ import io.neoterm.utils.TerminalUtils
class TerminalDialog(val context: Context) { class TerminalDialog(val context: Context) {
interface SessionFinishedCallback { interface SessionFinishedCallback {
fun onSessionFinished(dialog:TerminalDialog, finishedSession: TerminalSession?) fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?)
} }
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
private var view: View = LayoutInflater.from(context).inflate(R.layout.ui_term_dialog, null, false) private var view: View = LayoutInflater.from(context).inflate(R.layout.ui_term_dialog, null, false)
private var terminalView: TerminalView private var terminalView: TerminalView
private var terminalViewClient: BasicViewClient
private var terminalSessionCallback: BasicSessionCallback private var terminalSessionCallback: BasicSessionCallback
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
private var terminalSession: TerminalSession? = null private var terminalSession: TerminalSession? = null
@ -31,10 +30,8 @@ class TerminalDialog(val context: Context) {
init { init {
terminalView = view.findViewById(R.id.terminal_view_dialog) as TerminalView terminalView = view.findViewById(R.id.terminal_view_dialog) as TerminalView
terminalViewClient = BasicViewClient(terminalView) TerminalUtils.setupTerminalView(terminalView, BasicViewClient(terminalView))
TerminalUtils.setupTerminalView(terminalView, terminalViewClient)
terminalView.setTerminalViewClient(terminalViewClient)
terminalSessionCallback = object : BasicSessionCallback(terminalView) { terminalSessionCallback = object : BasicSessionCallback(terminalView) {
override fun onSessionFinished(finishedSession: TerminalSession?) { override fun onSessionFinished(finishedSession: TerminalSession?) {
sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession) sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession)
@ -56,7 +53,7 @@ class TerminalDialog(val context: Context) {
} }
.create() .create()
terminalSession = TerminalUtils.createSession(context, executablePath, arguments, null, null, null, terminalSessionCallback, false) terminalSession = TerminalUtils.createShellSession(context, executablePath, arguments, null, null, null, terminalSessionCallback, false)
terminalView.attachSession(terminalSession) terminalView.attachSession(terminalSession)
return this return this
} }
@ -66,12 +63,12 @@ class TerminalDialog(val context: Context) {
return this return this
} }
fun setTitle(title: String?) : TerminalDialog { fun setTitle(title: String?): TerminalDialog {
dialog?.setTitle(title) dialog?.setTitle(title)
return this return this
} }
fun onFinish(finishedCallback: SessionFinishedCallback):TerminalDialog { fun onFinish(finishedCallback: SessionFinishedCallback): TerminalDialog {
this.sessionFinishedCallback = finishedCallback this.sessionFinishedCallback = finishedCallback
return this return this
} }
@ -85,4 +82,12 @@ class TerminalDialog(val context: Context) {
dialog?.dismiss() dialog?.dismiss()
return this return this
} }
fun imeEnabled(enabled: Boolean): TerminalDialog {
if (enabled) {
terminalView.isFocusable = true
terminalView.isFocusableInTouchMode = true
}
return this
}
} }

View File

@ -286,7 +286,6 @@ public final class TerminalView extends View {
@Override @Override
public boolean commitText(CharSequence text, int newCursorPosition) { public boolean commitText(CharSequence text, int newCursorPosition) {
// TODO: AutoComplete
if (LOG_KEY_EVENTS) { if (LOG_KEY_EVENTS) {
Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")"); Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")");
} }

View File

@ -7,7 +7,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.0-alpha5' classpath 'com.android.tools.build:gradle:3.0.0-alpha7'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong