Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions termux/termux-app/src/main/java/com/termux/app/TermuxActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
Expand Down Expand Up @@ -468,7 +469,7 @@ public void onServiceConnected(ComponentName componentName, IBinder service) {
} else {

final Optional<TermuxSession> existingSession = workingDir == null ? Optional.empty() :
mTermuxService.getTermuxSessions().stream().filter(session -> Objects.equals(
mTermuxService.getTermuxSessionsListSnapshot().stream().filter(session -> Objects.equals(
session.getTerminalSession().getCwd(), workingDir)).findFirst();

setupTermuxSessionOnServiceConnected(
Expand Down Expand Up @@ -627,7 +628,7 @@ public void onResetTerminalSession() {

private void setTermuxSessionsListView() {
ListView termuxSessionsListView = findViewById(R.id.terminal_sessions_list);
mTermuxSessionListViewController = new TermuxSessionsListViewController(this, mTermuxService.getTermuxSessions());
mTermuxSessionListViewController = new TermuxSessionsListViewController(this, mTermuxService.getTermuxSessionsListSnapshot());
Comment thread
coderabbitai[bot] marked this conversation as resolved.
termuxSessionsListView.setAdapter(mTermuxSessionListViewController);
termuxSessionsListView.setOnItemClickListener(mTermuxSessionListViewController);
termuxSessionsListView.setOnItemLongClickListener(mTermuxSessionListViewController);
Expand Down Expand Up @@ -973,7 +974,20 @@ public boolean isTerminalToolbarTextInputViewSelected() {


public void termuxSessionListNotifyUpdated() {
TermuxExecutor.executeOnMain(() -> mTermuxSessionListViewController.notifyDataSetChanged());
// The session list may be mutated on a background thread (e.g. onCreateNewSession() runs
// createTermuxSession() on a background executor). Re-snapshotting the service's list into
// the adapter must therefore happen on the UI thread, so the adapter's own list is only
// ever read/written by the UI thread and the ListView can never observe a concurrent change.
if (Looper.myLooper() == Looper.getMainLooper()) {
refreshSessionsListView();
} else {
TermuxExecutor.executeOnMain(this::refreshSessionsListView);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

private void refreshSessionsListView() {
if (mTermuxSessionListViewController == null || mTermuxService == null) return;
mTermuxSessionListViewController.updateSessions(mTermuxService.getTermuxSessionsListSnapshot());
}

public boolean isVisible() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ public synchronized int removeTermuxSession(TerminalSession sessionToRemove) {

/** Callback received when a {@link TermuxSession} finishes. */
@Override
public void onTermuxSessionExited(final TermuxSession termuxSession) {
public synchronized void onTermuxSessionExited(final TermuxSession termuxSession) {
if (termuxSession != null) {
ExecutionCommand executionCommand = termuxSession.getExecutionCommand();

Expand Down Expand Up @@ -896,6 +896,16 @@ public synchronized List<TermuxSession> getTermuxSessions() {
return mShellManager.mTermuxSessions;
}

/**
* Returns a consistent copy of the current {@link TermuxSession} list. Unlike
* {@link #getTermuxSessions()}, this does not expose the live backing list, so it is safe to
* iterate from any thread (e.g. to feed a UI adapter). The copy is taken under the same monitor
* that guards all session mutations, so it can never observe a half-applied add/remove.
*/
public synchronized List<TermuxSession> getTermuxSessionsListSnapshot() {
return new ArrayList<>(mShellManager.mTermuxSessions);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@Nullable
public synchronized TermuxSession getTermuxSession(int index) {
if (index >= 0 && index < mShellManager.mTermuxSessions.size())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.termux.shared.theme.NightMode;
import com.termux.shared.theme.ThemeUtils;
import com.termux.terminal.TerminalSession;
import java.util.ArrayList;
import java.util.List;

public class TermuxSessionsListViewController extends ArrayAdapter<TermuxSession> implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {
Expand All @@ -32,10 +33,24 @@ public class TermuxSessionsListViewController extends ArrayAdapter<TermuxSession
final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);

public TermuxSessionsListViewController(TermuxActivity activity, List<TermuxSession> sessionList) {
super(activity.getApplicationContext(), R.layout.item_terminal_sessions_list, sessionList);
// Defensively copy so the adapter owns its data instead of sharing the service's live list.
// The backing list is then only ever touched on the UI thread (here and in updateSessions),
// so a background session add/remove can never mutate it while the ListView is laying out.
super(activity.getApplicationContext(), R.layout.item_terminal_sessions_list, new ArrayList<>(sessionList));
this.mActivity = activity;
}

/**
* Replace the adapter's session snapshot with a copy of {@code sessions} and refresh the
* ListView in a single notification. Must be called on the UI thread.
*/
public void updateSessions(@NonNull List<TermuxSession> sessions) {
setNotifyOnChange(false);
clear();
addAll(sessions);
notifyDataSetChanged();
}

@SuppressLint("SetTextI18n")
@NonNull
@Override
Expand Down
Loading