Skip to content
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />

<application
Expand All @@ -55,7 +56,7 @@
android:exported="true"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
android:label="@string/application_name"
android:launchMode="singleTask"
android:launchMode="standard"
android:resizeableActivity="true"
android:theme="@style/Theme.TermuxActivity.DayNight.NoActionBar"
tools:targetApi="n">
Expand Down
94 changes: 87 additions & 7 deletions app/src/main/java/com/termux/app/TermuxActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.view.ContextMenu;
Expand All @@ -22,6 +23,7 @@
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.Toast;
Expand Down Expand Up @@ -175,6 +177,11 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo

private float mTerminalToolbarDefaultHeight;

/**
* Unique identifier for this activity instance. Used for session attachment tracking.
*/
private final int mActivityId = System.identityHashCode(this);


private static final int CONTEXT_MENU_SELECT_URL_ID = 0;
private static final int CONTEXT_MENU_SHARE_TRANSCRIPT_ID = 1;
Expand Down Expand Up @@ -251,6 +258,8 @@ public void onCreate(Bundle savedInstanceState) {

setToggleKeyboardView();

setNewWindowButtonView();

registerForContextMenu(mTerminalView);

FileReceiverActivity.updateFileReceiverActivityComponentsState(this);
Expand Down Expand Up @@ -352,9 +361,22 @@ public void onDestroy() {

if (mIsInvalidState) return;

// Detach all sessions owned by this activity and reset their clients
if (mTermuxService != null) {
// Do not leave service and session clients with references to activity.
mTermuxService.unsetTermuxTerminalSessionClient();
TerminalSession currentSession = getCurrentSession();
if (currentSession != null) {
// Reset this session's client to the service client
mTermuxService.resetSessionClient(currentSession);
}
if (mTerminalView != null) {
mTerminalView.detachSession();
}

// Detach all sessions owned by this activity (single source of truth)
mTermuxService.detachAllSessionsForActivity(mActivityId);

// Remove this activity's client from the service's set
mTermuxService.removeTermuxTerminalSessionClient(mTermuxTerminalSessionActivityClient);
mTermuxService = null;
}

Expand All @@ -374,6 +396,23 @@ public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putBoolean(ARG_ACTIVITY_RECREATED, true);
}

@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
super.onMultiWindowModeChanged(isInMultiWindowMode);
Logger.logDebug(LOG_TAG, "onMultiWindowModeChanged: " + isInMultiWindowMode);
}

/** Add a new window */
public void addNewWindow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent intent = new Intent(this, TermuxActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT |
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
startActivity(intent);
}
}




Expand Down Expand Up @@ -417,11 +456,33 @@ public void onServiceConnected(ComponentName componentName, IBinder service) {
// then the original intent will be re-delivered, resulting in a new session being re-added
// each time.
if (!mIsActivityRecreated && intent != null && Intent.ACTION_RUN.equals(intent.getAction())) {
// Android 7.1 app shortcut from res/xml/shortcuts.xml.
boolean isFailSafe = intent.getBooleanExtra(TERMUX_ACTIVITY.EXTRA_FAILSAFE_SESSION, false);
mTermuxTerminalSessionActivityClient.addNewSession(isFailSafe, null);
} else {
mTermuxTerminalSessionActivityClient.setCurrentSession(mTermuxTerminalSessionActivityClient.getCurrentStoredSessionOrLast());
if (intent.getBooleanExtra(TERMUX_ACTIVITY.EXTRA_NEW_WINDOW, false)) {
// Android 7.1 app shortcut: open a new window
addNewWindow();
} else {
// Android 7.1 app shortcut from res/xml/shortcuts.xml.
boolean isFailSafe = intent.getBooleanExtra(TERMUX_ACTIVITY.EXTRA_FAILSAFE_SESSION, false);
mTermuxTerminalSessionActivityClient.addNewSession(isFailSafe, null);
}
}

// Ensure this activity is attached to a session
if (getCurrentSession() == null) {
TerminalSession sessionToAttach = mTermuxService.claimFirstUnattachedSession(mActivityId);
if (sessionToAttach != null) {
mTermuxTerminalSessionActivityClient.setCurrentSession(sessionToAttach);
} else {
// All sessions are attached, create a new one or use stored session
TerminalSession storedSession = mTermuxTerminalSessionActivityClient.getCurrentStoredSessionOrLast();
if (storedSession != null && !mTermuxService.isSessionAttached(storedSession)) {
mTermuxTerminalSessionActivityClient.setCurrentSession(storedSession);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInMultiWindowMode()) {
// In multi-window mode with all sessions attached, create a new session
mTermuxTerminalSessionActivityClient.addNewSession(false, null);
} else {
mTermuxTerminalSessionActivityClient.setCurrentSession(storedSession);
}
}
}
}

Expand Down Expand Up @@ -594,6 +655,21 @@ private void setToggleKeyboardView() {
});
}

private void setNewWindowButtonView() {
View newWindowButton = findViewById(R.id.new_window_button);
if (newWindowButton != null) {
newWindowButton.setOnClickListener(v -> addNewWindow());
// Show if multi-window API is available (N+)
newWindowButton.setVisibility(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ? View.VISIBLE : View.GONE);
}

// Use vertical orientation for drawer buttons if multi-window is available
LinearLayout drawerButtons = findViewById(R.id.drawer_buttons);
if (drawerButtons != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
drawerButtons.setOrientation(LinearLayout.VERTICAL);
}
}




Expand Down Expand Up @@ -877,6 +953,10 @@ public TermuxService getTermuxService() {
return mTermuxService;
}

public int getActivityId() {
return mActivityId;
}

public TerminalView getTerminalView() {
return mTerminalView;
}
Expand Down
Loading