mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-07 03:05:18 +08:00
Refactor TermuxService
- Dedicated functions have been created for various actions and commands. - The `startForeground()` call will be made both on `onCreate()` and `onStartCommand(). - The `stopForeground()` call will be made before every `onSelf()` call. - The references to `TermuxActivity` will now be removed in `onUnbind()` as well if activity `onDestroy()` failed to do so. - Appropriate log entries are added to help debug issues.
This commit is contained in:
@@ -63,12 +63,15 @@ public final class TermuxService extends Service {
|
||||
|
||||
/**
|
||||
* The terminal sessions which this service manages.
|
||||
* <p/>
|
||||
* Note that this list is observed by {@link TermuxActivity#mTermuxSessionListViewController}, so any changes must be made on the UI
|
||||
* thread and followed by a call to {@link ArrayAdapter#notifyDataSetChanged()} }.
|
||||
* Note that this list is observed by {@link TermuxActivity#mTermuxSessionListViewController},
|
||||
* so any changes must be made on the UI thread and followed by a call to
|
||||
* {@link ArrayAdapter#notifyDataSetChanged()} }.
|
||||
*/
|
||||
final List<TerminalSession> mTerminalSessions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The background jobs which this service manages.
|
||||
*/
|
||||
final List<BackgroundJob> mBackgroundTasks = new ArrayList<>();
|
||||
|
||||
/** The full implementation of the {@link TerminalSessionClient} interface to be used by {@link TerminalSession}
|
||||
@@ -86,89 +89,49 @@ public final class TermuxService extends Service {
|
||||
private PowerManager.WakeLock mWakeLock;
|
||||
private WifiManager.WifiLock mWifiLock;
|
||||
|
||||
/** If the user has executed the {@link TermuxConstants.TERMUX_APP.TERMUX_SERVICE#ACTION_STOP_SERVICE} intent. */
|
||||
/** If the user has executed the {@link TERMUX_SERVICE#ACTION_STOP_SERVICE} intent. */
|
||||
boolean mWantsToStop = false;
|
||||
|
||||
private static final String LOG_TAG = "TermuxService";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Logger.logVerbose(LOG_TAG, "onCreate");
|
||||
runStartForeground();
|
||||
}
|
||||
|
||||
@SuppressLint("Wakelock")
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Logger.logDebug(LOG_TAG, "onStartCommand");
|
||||
|
||||
// Run again in case service is already started and onCreate() is not called
|
||||
runStartForeground();
|
||||
|
||||
String action = intent.getAction();
|
||||
if (TERMUX_SERVICE.ACTION_STOP_SERVICE.equals(action)) {
|
||||
mWantsToStop = true;
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).finishIfRunning();
|
||||
stopSelf();
|
||||
} else if (TERMUX_SERVICE.ACTION_WAKE_LOCK.equals(action)) {
|
||||
if (mWakeLock == null) {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermuxConstants.TERMUX_APP_NAME.toLowerCase() + ":service-wakelock");
|
||||
mWakeLock.acquire();
|
||||
|
||||
// http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
|
||||
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TermuxConstants.TERMUX_APP_NAME.toLowerCase());
|
||||
mWifiLock.acquire();
|
||||
|
||||
String packageName = getPackageName();
|
||||
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||
Intent whitelist = new Intent();
|
||||
whitelist.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
whitelist.setData(Uri.parse("package:" + packageName));
|
||||
whitelist.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
try {
|
||||
startActivity(whitelist);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to call ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", e);
|
||||
}
|
||||
}
|
||||
|
||||
updateNotification();
|
||||
if (action != null) {
|
||||
switch (action) {
|
||||
case TERMUX_SERVICE.ACTION_STOP_SERVICE:
|
||||
Logger.logDebug(LOG_TAG, "ACTION_STOP_SERVICE intent received");
|
||||
actionStopService();
|
||||
break;
|
||||
case TERMUX_SERVICE.ACTION_WAKE_LOCK:
|
||||
Logger.logDebug(LOG_TAG, "ACTION_WAKE_LOCK intent received");
|
||||
actionAcquireWakeLock();
|
||||
break;
|
||||
case TERMUX_SERVICE.ACTION_WAKE_UNLOCK:
|
||||
Logger.logDebug(LOG_TAG, "ACTION_WAKE_UNLOCK intent received");
|
||||
actionReleaseWakeLock(true);
|
||||
break;
|
||||
case TERMUX_SERVICE.ACTION_SERVICE_EXECUTE:
|
||||
Logger.logDebug(LOG_TAG, "ACTION_SERVICE_EXECUTE intent received");
|
||||
actionServiceExecute(intent);
|
||||
break;
|
||||
default:
|
||||
Logger.logError(LOG_TAG, "Invalid action: \"" + action + "\"");
|
||||
break;
|
||||
}
|
||||
} else if (TERMUX_SERVICE.ACTION_WAKE_UNLOCK.equals(action)) {
|
||||
if (mWakeLock != null) {
|
||||
mWakeLock.release();
|
||||
mWakeLock = null;
|
||||
|
||||
mWifiLock.release();
|
||||
mWifiLock = null;
|
||||
|
||||
updateNotification();
|
||||
}
|
||||
} else if (TERMUX_SERVICE.ACTION_SERVICE_EXECUTE.equals(action)) {
|
||||
Uri executableUri = intent.getData();
|
||||
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
||||
|
||||
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(TERMUX_SERVICE.EXTRA_ARGUMENTS));
|
||||
String cwd = intent.getStringExtra(TERMUX_SERVICE.EXTRA_WORKDIR);
|
||||
|
||||
if (intent.getBooleanExtra(TERMUX_SERVICE.EXTRA_BACKGROUND, false)) {
|
||||
BackgroundJob task = new BackgroundJob(cwd, executablePath, arguments, this, intent.getParcelableExtra("pendingIntent"));
|
||||
mBackgroundTasks.add(task);
|
||||
updateNotification();
|
||||
} else {
|
||||
boolean failsafe = intent.getBooleanExtra(TERMUX_ACTIVITY.ACTION_FAILSAFE_SESSION, false);
|
||||
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, failsafe);
|
||||
|
||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||
if (executablePath != null) {
|
||||
int lastSlash = executablePath.lastIndexOf('/');
|
||||
String name = (lastSlash == -1) ? executablePath : executablePath.substring(lastSlash + 1);
|
||||
name = name.replace('-', ' ');
|
||||
newSession.mSessionName = name;
|
||||
}
|
||||
|
||||
// Make the newly created session the current one to be displayed:
|
||||
TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(this);
|
||||
preferences.setCurrentSession(newSession.mHandle);
|
||||
|
||||
// Launch the main Termux app, which will now show the current session:
|
||||
startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
} else if (action != null) {
|
||||
Logger.logError(LOG_TAG, "Unknown TermuxService action: '" + action + "'");
|
||||
}
|
||||
|
||||
// If this service really do get killed, there is no point restarting it automatically - let the user do on next
|
||||
@@ -176,27 +139,310 @@ public final class TermuxService extends Service {
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Logger.logVerbose(LOG_TAG, "onDestroy");
|
||||
File termuxTmpDir = TermuxConstants.TERMUX_TMP_DIR;
|
||||
|
||||
if (termuxTmpDir.exists()) {
|
||||
try {
|
||||
TermuxInstaller.deleteFolder(termuxTmpDir.getCanonicalFile());
|
||||
} catch (Exception e) {
|
||||
Logger.logStackTraceWithMessage(LOG_TAG, "Error while removing file at " + termuxTmpDir.getAbsolutePath(), e);
|
||||
}
|
||||
|
||||
termuxTmpDir.mkdirs();
|
||||
}
|
||||
|
||||
actionReleaseWakeLock(false);
|
||||
finishAllTerminalSessions();
|
||||
runStopForeground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
Logger.logVerbose(LOG_TAG, "onBind");
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
public boolean onUnbind(Intent intent) {
|
||||
Logger.logVerbose(LOG_TAG, "onUnbind");
|
||||
|
||||
// Since we cannot rely on {@link TermuxActivity.onDestroy()} to always complete,
|
||||
// we unset clients here as well if it failed, so that we do not leave service and session
|
||||
// clients with references to the activity.
|
||||
if(mTermuxSessionClient != null)
|
||||
unsetTermuxSessionClient();
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Make service run in foreground mode. */
|
||||
private void runStartForeground() {
|
||||
setupNotificationChannel();
|
||||
startForeground(NOTIFICATION_ID, buildNotification());
|
||||
}
|
||||
|
||||
/** Update the shown foreground service notification after making any changes that affect it. */
|
||||
void updateNotification() {
|
||||
if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) {
|
||||
// Exit if we are updating after the user disabled all locks with no sessions or tasks running.
|
||||
stopSelf();
|
||||
/** Make service leave foreground mode. */
|
||||
private void runStopForeground() {
|
||||
stopForeground(true);
|
||||
}
|
||||
|
||||
/** Request to stop service. */
|
||||
private void requestStopService() {
|
||||
Logger.logDebug(LOG_TAG, "Requesting to stop service");
|
||||
runStopForeground();
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Process action to stop service. */
|
||||
private void actionStopService() {
|
||||
mWantsToStop = true;
|
||||
finishAllTerminalSessions();
|
||||
requestStopService();
|
||||
}
|
||||
|
||||
/** Process action to acquire Power and Wi-Fi WakeLocks. */
|
||||
@SuppressLint({"WakelockTimeout", "BatteryLife"})
|
||||
private void actionAcquireWakeLock() {
|
||||
if (mWakeLock == null) {
|
||||
Logger.logDebug(LOG_TAG, "Acquiring WakeLocks");
|
||||
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermuxConstants.TERMUX_APP_NAME.toLowerCase() + ":service-wakelock");
|
||||
mWakeLock.acquire();
|
||||
|
||||
// http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
|
||||
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TermuxConstants.TERMUX_APP_NAME.toLowerCase());
|
||||
mWifiLock.acquire();
|
||||
|
||||
String packageName = getPackageName();
|
||||
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||
Intent whitelist = new Intent();
|
||||
whitelist.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
whitelist.setData(Uri.parse("package:" + packageName));
|
||||
whitelist.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
try {
|
||||
startActivity(whitelist);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to call ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", e);
|
||||
}
|
||||
}
|
||||
|
||||
updateNotification();
|
||||
|
||||
Logger.logDebug(LOG_TAG, "WakeLocks acquired successfully");
|
||||
} else {
|
||||
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, buildNotification());
|
||||
Logger.logDebug(LOG_TAG, "Ignoring acquiring WakeLocks since they are already held");
|
||||
}
|
||||
}
|
||||
|
||||
/** Process action to release Power and Wi-Fi WakeLocks. */
|
||||
private void actionReleaseWakeLock(boolean updateNotification) {
|
||||
if (mWakeLock == null && mWifiLock == null){
|
||||
Logger.logDebug(LOG_TAG, "Ignoring releasing WakeLocks since none are already held");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.logDebug(LOG_TAG, "Releasing WakeLocks");
|
||||
|
||||
if (mWakeLock != null) {
|
||||
mWakeLock.release();
|
||||
mWakeLock = null;
|
||||
}
|
||||
|
||||
if (mWifiLock != null) {
|
||||
mWifiLock.release();
|
||||
mWifiLock = null;
|
||||
}
|
||||
|
||||
if(updateNotification)
|
||||
updateNotification();
|
||||
|
||||
Logger.logDebug(LOG_TAG, "WakeLocks released successfully");
|
||||
}
|
||||
|
||||
/** Process action to execute a shell command in a foreground session or in background. */
|
||||
private void actionServiceExecute(Intent intent) {
|
||||
Uri executableUri = intent.getData();
|
||||
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
||||
|
||||
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(TERMUX_SERVICE.EXTRA_ARGUMENTS));
|
||||
String cwd = intent.getStringExtra(TERMUX_SERVICE.EXTRA_WORKDIR);
|
||||
|
||||
PendingIntent pendingIntent = intent.getParcelableExtra(TERMUX_SERVICE.EXTRA_PENDING_INTENT);
|
||||
|
||||
if (intent.getBooleanExtra(TERMUX_SERVICE.EXTRA_BACKGROUND, false)) {
|
||||
executeBackgroundCommand(executablePath, arguments, cwd, pendingIntent);
|
||||
} else {
|
||||
executeForegroundCommand(intent, executablePath, arguments, cwd);
|
||||
}
|
||||
}
|
||||
|
||||
/** Execute a shell command in background with {@link BackgroundJob}. */
|
||||
private void executeBackgroundCommand(String executablePath, String[] arguments, String cwd, PendingIntent pendingIntent) {
|
||||
Logger.logDebug(LOG_TAG, "Starting background command");
|
||||
|
||||
BackgroundJob task = new BackgroundJob(cwd, executablePath, arguments, this, pendingIntent);
|
||||
mBackgroundTasks.add(task);
|
||||
updateNotification();
|
||||
}
|
||||
|
||||
/** Callback received when a {@link BackgroundJob} finishes. */
|
||||
public void onBackgroundJobExited(final BackgroundJob task) {
|
||||
mHandler.post(() -> {
|
||||
mBackgroundTasks.remove(task);
|
||||
updateNotification();
|
||||
});
|
||||
}
|
||||
|
||||
/** Execute a shell command in a foreground terminal session. */
|
||||
private void executeForegroundCommand(Intent intent, String executablePath, String[] arguments, String cwd) {
|
||||
Logger.logDebug(LOG_TAG, "Starting foreground command");
|
||||
|
||||
boolean failsafe = intent.getBooleanExtra(TERMUX_ACTIVITY.ACTION_FAILSAFE_SESSION, false);
|
||||
TerminalSession newSession = createTerminalSession(executablePath, arguments, cwd, failsafe);
|
||||
|
||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||
if (executablePath != null) {
|
||||
int lastSlash = executablePath.lastIndexOf('/');
|
||||
String name = (lastSlash == -1) ? executablePath : executablePath.substring(lastSlash + 1);
|
||||
name = name.replace('-', ' ');
|
||||
newSession.mSessionName = name;
|
||||
}
|
||||
|
||||
// Make the newly created session the current one to be displayed:
|
||||
TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(this);
|
||||
preferences.setCurrentSession(newSession.mHandle);
|
||||
|
||||
// Launch the main Termux app, which will now show the current session:
|
||||
startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Create a terminal session. */
|
||||
public TerminalSession createTerminalSession(String executablePath, String[] arguments, String cwd, boolean failSafe) {
|
||||
TermuxConstants.TERMUX_HOME_DIR.mkdirs();
|
||||
|
||||
if (cwd == null || cwd.isEmpty()) cwd = TermuxConstants.TERMUX_HOME_DIR_PATH;
|
||||
|
||||
String[] env = BackgroundJob.buildEnvironment(failSafe, cwd);
|
||||
boolean isLoginShell = false;
|
||||
|
||||
if (executablePath == null) {
|
||||
if (!failSafe) {
|
||||
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
||||
File shellFile = new File(TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH, shellBinary);
|
||||
if (shellFile.canExecute()) {
|
||||
executablePath = shellFile.getAbsolutePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (executablePath == null) {
|
||||
// Fall back to system shell as last resort:
|
||||
executablePath = "/system/bin/sh";
|
||||
}
|
||||
isLoginShell = true;
|
||||
}
|
||||
|
||||
String[] processArgs = BackgroundJob.setupProcessArgs(executablePath, arguments);
|
||||
executablePath = processArgs[0];
|
||||
int lastSlashIndex = executablePath.lastIndexOf('/');
|
||||
String processName = (isLoginShell ? "-" : "") +
|
||||
(lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1));
|
||||
|
||||
String[] args = new String[processArgs.length];
|
||||
args[0] = processName;
|
||||
if (processArgs.length > 1) System.arraycopy(processArgs, 1, args, 1, processArgs.length - 1);
|
||||
|
||||
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, getTermuxSessionClient());
|
||||
mTerminalSessions.add(session);
|
||||
updateNotification();
|
||||
|
||||
// Make sure that terminal styling is always applied.
|
||||
Intent stylingIntent = new Intent(TERMUX_ACTIVITY.ACTION_RELOAD_STYLE);
|
||||
stylingIntent.putExtra(TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE, "styling");
|
||||
sendBroadcast(stylingIntent);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/** Remove a terminal session. */
|
||||
public int removeTerminalSession(TerminalSession sessionToRemove) {
|
||||
int indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove);
|
||||
mTerminalSessions.remove(indexOfRemoved);
|
||||
|
||||
if (mTerminalSessions.isEmpty() && mWakeLock == null) {
|
||||
// Finish if there are no sessions left and the wake lock is not held, otherwise keep the service alive if
|
||||
// holding wake lock since there may be daemon processes (e.g. sshd) running.
|
||||
requestStopService();
|
||||
} else {
|
||||
updateNotification();
|
||||
}
|
||||
return indexOfRemoved;
|
||||
}
|
||||
|
||||
/** Finish all terminal sessions by sending SIGKILL to their shells. */
|
||||
private void finishAllTerminalSessions() {
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).finishIfRunning();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** If {@link TermuxActivity} has not bound to the {@link TermuxService} yet or is destroyed, then
|
||||
* interface functions requiring the activity should not be available to the terminal sessions,
|
||||
* so we just return the {@link #mTermuxSessionClientBase}. Once {@link TermuxActivity} bind
|
||||
* callback is received, it should call {@link #setTermuxSessionClient} to set the
|
||||
* {@link TermuxService#mTermuxSessionClient} so that further terminal sessions are directly
|
||||
* passed the {@link TermuxSessionClient} object which fully implements the
|
||||
* {@link TerminalSessionClient} interface.
|
||||
*
|
||||
* @return Returns the {@link TermuxSessionClient} if {@link TermuxActivity} has bound with
|
||||
* {@link TermuxService}, otherwise {@link TermuxSessionClientBase}.
|
||||
*/
|
||||
public TermuxSessionClientBase getTermuxSessionClient() {
|
||||
if (mTermuxSessionClient != null)
|
||||
return mTermuxSessionClient;
|
||||
else
|
||||
return mTermuxSessionClientBase;
|
||||
}
|
||||
|
||||
/** This should be called when {@link TermuxActivity#onServiceConnected} is called to set the
|
||||
* {@link TermuxService#mTermuxSessionClient} variable and update the {@link TerminalSession}
|
||||
* and {@link TerminalEmulator} clients in case they were passed {@link TermuxSessionClientBase}
|
||||
* earlier.
|
||||
*
|
||||
* @param termuxSessionClient The {@link TermuxSessionClient} object that fully
|
||||
* implements the {@link TerminalSessionClient} interface.
|
||||
*/
|
||||
public void setTermuxSessionClient(TermuxSessionClient termuxSessionClient) {
|
||||
mTermuxSessionClient = termuxSessionClient;
|
||||
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).updateTerminalSessionClient(mTermuxSessionClient);
|
||||
}
|
||||
|
||||
/** This should be called when {@link TermuxActivity} has been destroyed and in {@link #onUnbind(Intent)}
|
||||
* so that the {@link TermuxService} and {@link TerminalSession} and {@link TerminalEmulator}
|
||||
* clients do not hold an activity references.
|
||||
*/
|
||||
public void unsetTermuxSessionClient() {
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).updateTerminalSessionClient(mTermuxSessionClientBase);
|
||||
|
||||
mTermuxSessionClient = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Notification buildNotification() {
|
||||
Intent notifyIntent = new Intent(this, TermuxActivity.class);
|
||||
// PendingIntent#getActivity(): "Note that the activity will be started outside of the context of an existing
|
||||
@@ -250,145 +496,6 @@ public final class TermuxService extends Service {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
File termuxTmpDir = TermuxConstants.TERMUX_TMP_DIR;
|
||||
|
||||
if (termuxTmpDir.exists()) {
|
||||
try {
|
||||
TermuxInstaller.deleteFolder(termuxTmpDir.getCanonicalFile());
|
||||
} catch (Exception e) {
|
||||
Logger.logStackTraceWithMessage(LOG_TAG, "Error while removing file at " + termuxTmpDir.getAbsolutePath(), e);
|
||||
}
|
||||
|
||||
termuxTmpDir.mkdirs();
|
||||
}
|
||||
|
||||
if (mWakeLock != null) mWakeLock.release();
|
||||
if (mWifiLock != null) mWifiLock.release();
|
||||
|
||||
stopForeground(true);
|
||||
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).finishIfRunning();
|
||||
}
|
||||
|
||||
public List<TerminalSession> getSessions() {
|
||||
return mTerminalSessions;
|
||||
}
|
||||
|
||||
public TerminalSession createTermSession(String executablePath, String[] arguments, String cwd, boolean failSafe) {
|
||||
TermuxConstants.TERMUX_HOME_DIR.mkdirs();
|
||||
|
||||
if (cwd == null || cwd.isEmpty()) cwd = TermuxConstants.TERMUX_HOME_DIR_PATH;
|
||||
|
||||
String[] env = BackgroundJob.buildEnvironment(failSafe, cwd);
|
||||
boolean isLoginShell = false;
|
||||
|
||||
if (executablePath == null) {
|
||||
if (!failSafe) {
|
||||
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
||||
File shellFile = new File(TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH, shellBinary);
|
||||
if (shellFile.canExecute()) {
|
||||
executablePath = shellFile.getAbsolutePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (executablePath == null) {
|
||||
// Fall back to system shell as last resort:
|
||||
executablePath = "/system/bin/sh";
|
||||
}
|
||||
isLoginShell = true;
|
||||
}
|
||||
|
||||
String[] processArgs = BackgroundJob.setupProcessArgs(executablePath, arguments);
|
||||
executablePath = processArgs[0];
|
||||
int lastSlashIndex = executablePath.lastIndexOf('/');
|
||||
String processName = (isLoginShell ? "-" : "") +
|
||||
(lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1));
|
||||
|
||||
String[] args = new String[processArgs.length];
|
||||
args[0] = processName;
|
||||
if (processArgs.length > 1) System.arraycopy(processArgs, 1, args, 1, processArgs.length - 1);
|
||||
|
||||
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, getTermuxSessionClient());
|
||||
mTerminalSessions.add(session);
|
||||
updateNotification();
|
||||
|
||||
// Make sure that terminal styling is always applied.
|
||||
Intent stylingIntent = new Intent(TERMUX_ACTIVITY.ACTION_RELOAD_STYLE);
|
||||
stylingIntent.putExtra(TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE, "styling");
|
||||
sendBroadcast(stylingIntent);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
public int removeTermSession(TerminalSession sessionToRemove) {
|
||||
int indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove);
|
||||
mTerminalSessions.remove(indexOfRemoved);
|
||||
if (mTerminalSessions.isEmpty() && mWakeLock == null) {
|
||||
// Finish if there are no sessions left and the wake lock is not held, otherwise keep the service alive if
|
||||
// holding wake lock since there may be daemon processes (e.g. sshd) running.
|
||||
stopSelf();
|
||||
} else {
|
||||
updateNotification();
|
||||
}
|
||||
return indexOfRemoved;
|
||||
}
|
||||
|
||||
/** If {@link TermuxActivity} has not bound to the {@link TermuxService} yet or is destroyed, then
|
||||
* interface functions requiring the activity should not be available to the terminal sessions,
|
||||
* so we just return the {@link #mTermuxSessionClientBase}. Once {@link TermuxActivity} bind
|
||||
* callback is received, it should call {@link #setTermuxSessionClient} to set the
|
||||
* {@link TermuxService#mTermuxSessionClient} so that further terminal sessions are directly
|
||||
* passed the {@link TermuxSessionClient} object which fully implements the
|
||||
* {@link TerminalSessionClient} interface.
|
||||
*
|
||||
* @return Returns the {@link TermuxSessionClient} if {@link TermuxActivity} has bound with
|
||||
* {@link TermuxService}, otherwise {@link TermuxSessionClientBase}.
|
||||
*/
|
||||
public TermuxSessionClientBase getTermuxSessionClient() {
|
||||
if (mTermuxSessionClient != null)
|
||||
return mTermuxSessionClient;
|
||||
else
|
||||
return mTermuxSessionClientBase;
|
||||
}
|
||||
|
||||
/** This should be called when {@link TermuxActivity#onServiceConnected} is called to set the
|
||||
* {@link TermuxService#mTermuxSessionClient} variable and update the {@link TerminalSession}
|
||||
* and {@link TerminalEmulator} clients in case they were passed {@link TermuxSessionClientBase}
|
||||
* earlier.
|
||||
*
|
||||
* @param termuxSessionClient The {@link TermuxSessionClient} object that fully
|
||||
* implements the {@link TerminalSessionClient} interface.
|
||||
*/
|
||||
public void setTermuxSessionClient(TermuxSessionClient termuxSessionClient) {
|
||||
mTermuxSessionClient = termuxSessionClient;
|
||||
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).updateTerminalSessionClient(mTermuxSessionClient);
|
||||
}
|
||||
|
||||
/** This should be called when {@link TermuxActivity} has been destroyed so that the
|
||||
* {@link TermuxService} and {@link TerminalSession} and {@link TerminalEmulator} clients do not
|
||||
* hold an activity references.
|
||||
*/
|
||||
public void unsetTermuxSessionClient() {
|
||||
mTermuxSessionClient = null;
|
||||
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).updateTerminalSessionClient(mTermuxSessionClientBase);
|
||||
}
|
||||
|
||||
public void onBackgroundJobExited(final BackgroundJob task) {
|
||||
mHandler.post(() -> {
|
||||
mBackgroundTasks.remove(task);
|
||||
updateNotification();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
|
||||
|
||||
@@ -402,7 +509,24 @@ public final class TermuxService extends Service {
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
/** Update the shown foreground service notification after making any changes that affect it. */
|
||||
void updateNotification() {
|
||||
if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) {
|
||||
// Exit if we are updating after the user disabled all locks with no sessions or tasks running.
|
||||
requestStopService();
|
||||
} else {
|
||||
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, buildNotification());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean wantsToStop() {
|
||||
return mWantsToStop;
|
||||
}
|
||||
|
||||
public List<TerminalSession> getSessions() {
|
||||
return mTerminalSessions;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -153,9 +153,10 @@ public class TermuxSessionClient extends TermuxSessionClientBase {
|
||||
final int indexOfSession = mActivity.getTermuxService().getSessions().indexOf(session);
|
||||
mActivity.showToast(toToastTitle(session), false);
|
||||
mActivity.terminalSessionListNotifyUpdated();
|
||||
final ListView lv = mActivity.findViewById(R.id.terminal_sessions_list);
|
||||
lv.setItemChecked(indexOfSession, true);
|
||||
lv.smoothScrollToPosition(indexOfSession);
|
||||
|
||||
final ListView termuxSessionsListView = mActivity.findViewById(R.id.terminal_sessions_list);
|
||||
termuxSessionsListView.setItemChecked(indexOfSession, true);
|
||||
termuxSessionsListView.smoothScrollToPosition(indexOfSession);
|
||||
}
|
||||
|
||||
public void switchToSession(boolean forward) {
|
||||
@@ -195,7 +196,7 @@ public class TermuxSessionClient extends TermuxSessionClientBase {
|
||||
workingDirectory = currentSession.getCwd();
|
||||
}
|
||||
|
||||
TerminalSession newSession = mActivity.getTermuxService().createTermSession(null, null, workingDirectory, failSafe);
|
||||
TerminalSession newSession = mActivity.getTermuxService().createTerminalSession(null, null, workingDirectory, failSafe);
|
||||
if (sessionName != null) {
|
||||
newSession.mSessionName = sessionName;
|
||||
}
|
||||
@@ -248,7 +249,7 @@ public class TermuxSessionClient extends TermuxSessionClientBase {
|
||||
// Return pressed with finished session - remove it.
|
||||
TermuxService service = mActivity.getTermuxService();
|
||||
|
||||
int index = service.removeTermSession(finishedSession);
|
||||
int index = service.removeTerminalSession(finishedSession);
|
||||
mActivity.terminalSessionListNotifyUpdated();
|
||||
if (mActivity.getTermuxService().getSessions().isEmpty()) {
|
||||
// There are no sessions to show, so finish the activity.
|
||||
|
Reference in New Issue
Block a user