Feature: Call NeoTerm from another app
This commit is contained in:
17
app/src/main/aidl/io/neoterm/services/INeoTermEmbedded.aidl
Normal file
17
app/src/main/aidl/io/neoterm/services/INeoTermEmbedded.aidl
Normal file
@ -0,0 +1,17 @@
|
||||
// INeoTermEmbedded.aidl
|
||||
package io.neoterm.services;
|
||||
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
// Declare any non-default types here with import statements
|
||||
|
||||
interface INeoTermEmbedded {
|
||||
// write command text to terminal
|
||||
void writeToTerminal(String sessionName, String command);
|
||||
|
||||
void onCreate(String sessionName, in ResultReceiver resultReceiver);
|
||||
void onVisible(String sessionName);
|
||||
void layoutView(String sessionName, int x, int y, int width, int height);
|
||||
void onInVisible(String sessionName);
|
||||
void onDestroyed(String sessionName);
|
||||
}
|
@ -178,6 +178,10 @@ object NeoPreference {
|
||||
return if (file.canExecute()) file.absolutePath else null
|
||||
}
|
||||
|
||||
fun getFontSize(): Int {
|
||||
return loadInt(NeoPreference.KEY_FONT_SIZE, 30)
|
||||
}
|
||||
|
||||
|
||||
// fun storeWindowSize(context: Context, width: Int, height: Int) {
|
||||
// store(KEY_FLOATING_WIDTH, width)
|
||||
|
217
app/src/main/java/io/neoterm/services/NeoTermEmbeddedService.kt
Normal file
217
app/src/main/java/io/neoterm/services/NeoTermEmbeddedService.kt
Normal file
@ -0,0 +1,217 @@
|
||||
package io.neoterm.services
|
||||
|
||||
import android.app.Service
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Binder
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.os.ResultReceiver
|
||||
import android.text.TextUtils
|
||||
import android.util.ArrayMap
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
|
||||
import io.neoterm.R
|
||||
import io.neoterm.backend.TerminalSession
|
||||
import io.neoterm.frontend.preference.NeoPreference
|
||||
import io.neoterm.frontend.shell.ShellParameter
|
||||
import io.neoterm.frontend.terminal.TerminalView
|
||||
|
||||
class NeoTermEmbeddedService : Service() {
|
||||
private var mTermuxService: NeoTermService? = null
|
||||
private val mSessions = ArrayMap<String, TerminalSession>()
|
||||
private val mResultReceivers = ArrayMap<String, ResultReceiver>()
|
||||
private var mTerminalView: TerminalView? = null
|
||||
private var mRootView: RootView? = null
|
||||
private var mWindowManager: WindowManager? = null
|
||||
|
||||
private var mHandler: Handler? = null
|
||||
private val mLock = Any()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
bindService(Intent(this, NeoTermService::class.java), mServiceConnection, Context.BIND_AUTO_CREATE)
|
||||
mHandler = Handler()
|
||||
mWindowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return mBinder
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent): Boolean {
|
||||
val sessionName = intent.getStringExtra("sessionName")
|
||||
if (!TextUtils.isEmpty(sessionName)) {
|
||||
destroySession(sessionName)
|
||||
}
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
private fun makeSureViewExit() {
|
||||
if (mRootView == null) {
|
||||
mRootView = RootView(this)
|
||||
val params = WindowManager.LayoutParams()
|
||||
params.type = WindowManager.LayoutParams.TYPE_PHONE
|
||||
params.width = 800
|
||||
params.height = 800
|
||||
params.gravity = Gravity.START or Gravity.TOP
|
||||
params.flags = params.flags or (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||
mWindowManager!!.addView(mRootView, params)
|
||||
}
|
||||
if (mTerminalView == null) {
|
||||
val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
mTerminalView = inflater.inflate(R.layout.ui_term_embedded, mRootView, false) as TerminalView
|
||||
mTerminalView!!.textSize = NeoPreference.getFontSize()
|
||||
mRootView!!.addView(mTerminalView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
unbindService(mServiceConnection)
|
||||
if (mSessions.size > 0) {
|
||||
mSessions.forEach {
|
||||
it.value.finishIfRunning();
|
||||
}
|
||||
}
|
||||
mSessions.clear();
|
||||
mResultReceivers.clear();
|
||||
}
|
||||
|
||||
private val mServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
mTermuxService = (service as NeoTermService.NeoTermBinder).service
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName) {
|
||||
mTermuxService = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNewSession(sessionName: String?): TerminalSession {
|
||||
val terminalSession = mTermuxService!!.createTermSession(ShellParameter().systemShell(false))
|
||||
if (sessionName != null) {
|
||||
terminalSession.mSessionName = sessionName
|
||||
}
|
||||
return terminalSession
|
||||
}
|
||||
|
||||
private fun findSession(sessionName: String): TerminalSession? {
|
||||
return mSessions[sessionName]
|
||||
}
|
||||
|
||||
// called from main thread
|
||||
private fun attachSessionAndVisible(session: TerminalSession) {
|
||||
mTerminalView!!.attachSession(session)
|
||||
mRootView!!.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun onSessionDestroyed(session: TerminalSession) {
|
||||
synchronized(mLock) {
|
||||
mTermuxService!!.removeTermSession(session)
|
||||
if (mSessions.size == 0) {
|
||||
// no session now
|
||||
if (mRootView != null) {
|
||||
mWindowManager!!.removeView(mRootView)
|
||||
}
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun layoutView(x: Int, y: Int, width: Int, height: Int) {
|
||||
if (mRootView != null) {
|
||||
val params = mRootView!!.layoutParams as WindowManager.LayoutParams
|
||||
params.width = width
|
||||
params.height = height
|
||||
mRootView!!.layoutParams = params
|
||||
params.x = x
|
||||
params.y = y
|
||||
mWindowManager!!.updateViewLayout(mRootView, params)
|
||||
}
|
||||
}
|
||||
|
||||
private val mBinder = object : INeoTermEmbedded.Stub() {
|
||||
@Throws(RemoteException::class)
|
||||
override fun writeToTerminal(sessionName: String, command: String) {
|
||||
mHandler!!.post { mTerminalView!!.currentSession.write(command) }
|
||||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun onCreate(sessionName: String, resultReceiver: ResultReceiver) {
|
||||
synchronized(mLock) {
|
||||
if (mSessions[sessionName] != null) {
|
||||
throw RemoteException("sessionName has been created")
|
||||
}
|
||||
}
|
||||
mHandler!!.post {
|
||||
synchronized(mLock) {
|
||||
val session = createNewSession(sessionName)
|
||||
mSessions.put(sessionName, session)
|
||||
mResultReceivers.put(sessionName, resultReceiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun onVisible(sessionName: String) {
|
||||
synchronized(mLock) {
|
||||
val session = findSession(sessionName)
|
||||
mHandler!!.post {
|
||||
makeSureViewExit()
|
||||
attachSessionAndVisible(session!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun layoutView(sessionName: String, x: Int, y: Int, width: Int, height: Int) {
|
||||
mHandler!!.post { this@NeoTermEmbeddedService.layoutView(x, y, width, height) }
|
||||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun onInVisible(sessionName: String) {
|
||||
synchronized(mLock) {
|
||||
val session = findSession(sessionName)
|
||||
mHandler!!.post { mRootView!!.visibility = View.GONE }
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun onDestroyed(sessionName: String) {
|
||||
destroySession(sessionName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun destroySession(sessionName: String) {
|
||||
synchronized(mLock) {
|
||||
val session = findSession(sessionName) ?: return
|
||||
mSessions.remove(sessionName)
|
||||
mResultReceivers.remove(sessionName)
|
||||
mHandler!!.post { onSessionDestroyed(session) }
|
||||
}
|
||||
}
|
||||
|
||||
// just like rootView in activity
|
||||
private inner class RootView(context: Context) : FrameLayout(context) {
|
||||
|
||||
init {
|
||||
requestFocus()
|
||||
isFocusable = true
|
||||
isFocusableInTouchMode = false
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ import io.neoterm.frontend.terminal.eks.ExtraKeysView
|
||||
*/
|
||||
object TerminalUtils {
|
||||
fun setupTerminalView(terminalView: TerminalView?, terminalViewClient: TerminalViewClient? = null) {
|
||||
terminalView?.textSize = NeoPreference.loadInt(NeoPreference.KEY_FONT_SIZE, 30)
|
||||
terminalView?.textSize = NeoPreference.getFontSize();
|
||||
|
||||
val fontComponent = ComponentManager.getComponent<FontComponent>()
|
||||
fontComponent.applyFont(terminalView, null, fontComponent.getCurrentFont())
|
||||
|
16
app/src/main/res/layout/ui_term_embedded.xml
Normal file
16
app/src/main/res/layout/ui_term_embedded.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<io.neoterm.frontend.terminal.TerminalView
|
||||
android:id="@+id/terminal_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/terminal_background"
|
||||
android:fadeScrollbars="true"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</RelativeLayout>
|
Reference in New Issue
Block a user