termux-app/app/src/main/java/com/termux/app/TermuxActivity.java

888 lines
32 KiB
Java

package com.termux.app;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.autofill.AutofillManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.Toast;
import com.termux.R;
import com.termux.app.terminal.TermuxActivityRootView;
import com.termux.shared.packages.PermissionUtils;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY;
import com.termux.app.activities.HelpActivity;
import com.termux.app.activities.SettingsActivity;
import com.termux.shared.settings.preferences.TermuxAppSharedPreferences;
import com.termux.app.terminal.TermuxSessionsListViewController;
import com.termux.app.terminal.io.TerminalToolbarViewPager;
import com.termux.app.terminal.TermuxTerminalSessionClient;
import com.termux.app.terminal.TermuxTerminalViewClient;
import com.termux.app.terminal.io.extrakeys.ExtraKeysView;
import com.termux.app.settings.properties.TermuxAppSharedProperties;
import com.termux.shared.interact.DialogUtils;
import com.termux.shared.logger.Logger;
import com.termux.shared.termux.TermuxUtils;
import com.termux.terminal.TerminalSession;
import com.termux.terminal.TerminalSessionClient;
import com.termux.app.utils.CrashUtils;
import com.termux.view.TerminalView;
import com.termux.view.TerminalViewClient;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.viewpager.widget.ViewPager;
/**
* A terminal emulator activity.
* <p/>
* See
* <ul>
* <li>http://www.mongrel-phones.com.au/default/how_to_make_a_local_service_and_bind_to_it_in_android</li>
* <li>https://code.google.com/p/android/issues/detail?id=6426</li>
* </ul>
* about memory leaks.
*/
public final class TermuxActivity extends Activity implements ServiceConnection {
/**
* The connection to the {@link TermuxService}. Requested in {@link #onCreate(Bundle)} with a call to
* {@link #bindService(Intent, ServiceConnection, int)}, and obtained and stored in
* {@link #onServiceConnected(ComponentName, IBinder)}.
*/
TermuxService mTermuxService;
/**
* The {@link TerminalView} shown in {@link TermuxActivity} that displays the terminal.
*/
TerminalView mTerminalView;
/**
* The {@link TerminalViewClient} interface implementation to allow for communication between
* {@link TerminalView} and {@link TermuxActivity}.
*/
TermuxTerminalViewClient mTermuxTerminalViewClient;
/**
* The {@link TerminalSessionClient} interface implementation to allow for communication between
* {@link TerminalSession} and {@link TermuxActivity}.
*/
TermuxTerminalSessionClient mTermuxTerminalSessionClient;
/**
* Termux app shared preferences manager.
*/
private TermuxAppSharedPreferences mPreferences;
/**
* Termux app shared properties manager, loaded from termux.properties
*/
private TermuxAppSharedProperties mProperties;
/**
* The root view of the {@link TermuxActivity}.
*/
TermuxActivityRootView mTermuxActivityRootView;
/**
* The space at the bottom of {@link @mTermuxActivityRootView} of the {@link TermuxActivity}.
*/
View mTermuxActivityBottomSpaceView;
/**
* The terminal extra keys view.
*/
ExtraKeysView mExtraKeysView;
/**
* The termux sessions list controller.
*/
TermuxSessionsListViewController mTermuxSessionListViewController;
/**
* The {@link TermuxActivity} broadcast receiver for various things like terminal style configuration changes.
*/
private final BroadcastReceiver mTermuxActivityBroadcastReceiver = new TermuxActivityBroadcastReceiver();
/**
* The last toast shown, used cancel current toast before showing new in {@link #showToast(String, boolean)}.
*/
Toast mLastToast;
/**
* If between onResume() and onStop(). Note that only one session is in the foreground of the terminal view at the
* time, so if the session causing a change is not in the foreground it should probably be treated as background.
*/
private boolean mIsVisible;
/**
* If onResume() was called after onCreate().
*/
private boolean isOnResumeAfterOnCreate = false;
/**
* The {@link TermuxActivity} is in an invalid state and must not be run.
*/
private boolean mIsInvalidState;
private int mNavBarHeight;
private int mTerminalToolbarDefaultHeight;
private static final int CONTEXT_MENU_SELECT_URL_ID = 0;
private static final int CONTEXT_MENU_SHARE_TRANSCRIPT_ID = 1;
private static final int CONTEXT_MENU_AUTOFILL_ID = 2;
private static final int CONTEXT_MENU_RESET_TERMINAL_ID = 3;
private static final int CONTEXT_MENU_KILL_PROCESS_ID = 4;
private static final int CONTEXT_MENU_STYLING_ID = 5;
private static final int CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON = 6;
private static final int CONTEXT_MENU_HELP_ID = 7;
private static final int CONTEXT_MENU_SETTINGS_ID = 8;
private static final int CONTEXT_MENU_REPORT_ID = 9;
private static final String ARG_TERMINAL_TOOLBAR_TEXT_INPUT = "terminal_toolbar_text_input";
private static final String LOG_TAG = "TermuxActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
Logger.logDebug(LOG_TAG, "onCreate");
isOnResumeAfterOnCreate = true;
// Check if a crash happened on last run of the app and show a
// notification with the crash details if it did
CrashUtils.notifyAppCrashOnLastRun(this, LOG_TAG);
// Load termux shared properties
mProperties = new TermuxAppSharedProperties(this);
setActivityTheme();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_termux);
// Load termux shared preferences
// This will also fail if TermuxConstants.TERMUX_PACKAGE_NAME does not equal applicationId
mPreferences = TermuxAppSharedPreferences.build(this, true);
if (mPreferences == null) {
// An AlertDialog should have shown to kill the app, so we don't continue running activity code
mIsInvalidState = true;
return;
}
mTermuxActivityRootView = findViewById(R.id.activity_termux_root_view);
mTermuxActivityRootView.setActivity(this);
mTermuxActivityBottomSpaceView = findViewById(R.id.activity_termux_bottom_space_view);
View content = findViewById(android.R.id.content);
content.setOnApplyWindowInsetsListener((v, insets) -> {
mNavBarHeight = insets.getSystemWindowInsetBottom();
return insets;
});
if (mProperties.isUsingFullScreen()) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
setDrawerTheme();
setTermuxTerminalViewAndClients();
setTerminalToolbarView(savedInstanceState);
setSettingsButtonView();
setNewSessionButtonView();
setToggleKeyboardView();
registerForContextMenu(mTerminalView);
// Start the {@link TermuxService} and make it run regardless of who is bound to it
Intent serviceIntent = new Intent(this, TermuxService.class);
startService(serviceIntent);
// Attempt to bind to the service, this will call the {@link #onServiceConnected(ComponentName, IBinder)}
// callback if it succeeds.
if (!bindService(serviceIntent, this, 0))
throw new RuntimeException("bindService() failed");
// Send the {@link TermuxConstants#BROADCAST_TERMUX_OPENED} broadcast to notify apps that Termux
// app has been opened.
TermuxUtils.sendTermuxOpenedBroadcast(this);
}
@Override
public void onStart() {
super.onStart();
Logger.logDebug(LOG_TAG, "onStart");
if (mIsInvalidState) return;
mIsVisible = true;
if (mTermuxTerminalSessionClient != null)
mTermuxTerminalSessionClient.onStart();
if (mTermuxTerminalViewClient != null)
mTermuxTerminalViewClient.onStart();
addTermuxActivityRootViewGlobalLayoutListener();
registerTermuxActivityBroadcastReceiver();
}
@Override
public void onResume() {
super.onResume();
Logger.logVerbose(LOG_TAG, "onResume");
if (mIsInvalidState) return;
if (mTermuxTerminalSessionClient != null)
mTermuxTerminalSessionClient.onResume();
if (mTermuxTerminalViewClient != null)
mTermuxTerminalViewClient.onResume();
isOnResumeAfterOnCreate = false;
}
@Override
protected void onStop() {
super.onStop();
Logger.logDebug(LOG_TAG, "onStop");
if (mIsInvalidState) return;
mIsVisible = false;
if (mTermuxTerminalSessionClient != null)
mTermuxTerminalSessionClient.onStop();
if (mTermuxTerminalViewClient != null)
mTermuxTerminalViewClient.onStop();
removeTermuxActivityRootViewGlobalLayoutListener();
unregisterTermuxActivityBroadcastReceiever();
getDrawer().closeDrawers();
}
@Override
public void onDestroy() {
super.onDestroy();
Logger.logDebug(LOG_TAG, "onDestroy");
if (mIsInvalidState) return;
if (mTermuxService != null) {
// Do not leave service and session clients with references to activity.
mTermuxService.unsetTermuxTerminalSessionClient();
mTermuxService = null;
}
try {
unbindService(this);
} catch (Exception e) {
// ignore.
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
saveTerminalToolbarTextInput(savedInstanceState);
}
/**
* Part of the {@link ServiceConnection} interface. The service is bound with
* {@link #bindService(Intent, ServiceConnection, int)} in {@link #onCreate(Bundle)} which will cause a call to this
* callback method.
*/
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
Logger.logDebug(LOG_TAG, "onServiceConnected");
mTermuxService = ((TermuxService.LocalBinder) service).service;
setTermuxSessionsListView();
if (mTermuxService.isTermuxSessionsEmpty()) {
if (mIsVisible) {
TermuxInstaller.setupBootstrapIfNeeded(TermuxActivity.this, () -> {
if (mTermuxService == null) return; // Activity might have been destroyed.
try {
Bundle bundle = getIntent().getExtras();
boolean launchFailsafe = false;
if (bundle != null) {
launchFailsafe = bundle.getBoolean(TERMUX_ACTIVITY.ACTION_FAILSAFE_SESSION, false);
}
mTermuxTerminalSessionClient.addNewSession(launchFailsafe, null);
} catch (WindowManager.BadTokenException e) {
// Activity finished - ignore.
}
});
} else {
// The service connected while not in foreground - just bail out.
finishActivityIfNotFinishing();
}
} else {
Intent i = getIntent();
if (i != null && Intent.ACTION_RUN.equals(i.getAction())) {
// Android 7.1 app shortcut from res/xml/shortcuts.xml.
boolean isFailSafe = i.getBooleanExtra(TERMUX_ACTIVITY.ACTION_FAILSAFE_SESSION, false);
mTermuxTerminalSessionClient.addNewSession(isFailSafe, null);
} else {
mTermuxTerminalSessionClient.setCurrentSession(mTermuxTerminalSessionClient.getCurrentStoredSessionOrLast());
}
}
// Update the {@link TerminalSession} and {@link TerminalEmulator} clients.
mTermuxService.setTermuxTerminalSessionClient(mTermuxTerminalSessionClient);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Logger.logDebug(LOG_TAG, "onServiceDisconnected");
// Respect being stopped from the {@link TermuxService} notification action.
finishActivityIfNotFinishing();
}
private void setActivityTheme() {
if (mProperties.isUsingBlackUI()) {
this.setTheme(R.style.Theme_Termux_Black);
} else {
this.setTheme(R.style.Theme_Termux);
}
}
private void setDrawerTheme() {
if (mProperties.isUsingBlackUI()) {
findViewById(R.id.left_drawer).setBackgroundColor(ContextCompat.getColor(this,
android.R.color.background_dark));
((ImageButton) findViewById(R.id.settings_button)).setColorFilter(Color.WHITE);
}
}
public void addTermuxActivityRootViewGlobalLayoutListener() {
getTermuxActivityRootView().getViewTreeObserver().addOnGlobalLayoutListener(getTermuxActivityRootView());
}
public void removeTermuxActivityRootViewGlobalLayoutListener() {
if (getTermuxActivityRootView() != null)
getTermuxActivityRootView().getViewTreeObserver().removeOnGlobalLayoutListener(getTermuxActivityRootView());
}
private void setTermuxTerminalViewAndClients() {
// Set termux terminal view and session clients
mTermuxTerminalSessionClient = new TermuxTerminalSessionClient(this);
mTermuxTerminalViewClient = new TermuxTerminalViewClient(this, mTermuxTerminalSessionClient);
// Set termux terminal view
mTerminalView = findViewById(R.id.terminal_view);
mTerminalView.setTerminalViewClient(mTermuxTerminalViewClient);
if (mTermuxTerminalViewClient != null)
mTermuxTerminalViewClient.onCreate();
if (mTermuxTerminalSessionClient != null)
mTermuxTerminalSessionClient.onCreate();
}
private void setTermuxSessionsListView() {
ListView termuxSessionsListView = findViewById(R.id.terminal_sessions_list);
mTermuxSessionListViewController = new TermuxSessionsListViewController(this, mTermuxService.getTermuxSessions());
termuxSessionsListView.setAdapter(mTermuxSessionListViewController);
termuxSessionsListView.setOnItemClickListener(mTermuxSessionListViewController);
termuxSessionsListView.setOnItemLongClickListener(mTermuxSessionListViewController);
}
private void setTerminalToolbarView(Bundle savedInstanceState) {
final ViewPager terminalToolbarViewPager = findViewById(R.id.terminal_toolbar_view_pager);
if (mPreferences.shouldShowTerminalToolbar()) terminalToolbarViewPager.setVisibility(View.VISIBLE);
ViewGroup.LayoutParams layoutParams = terminalToolbarViewPager.getLayoutParams();
mTerminalToolbarDefaultHeight = layoutParams.height;
setTerminalToolbarHeight();
String savedTextInput = null;
if (savedInstanceState != null)
savedTextInput = savedInstanceState.getString(ARG_TERMINAL_TOOLBAR_TEXT_INPUT);
terminalToolbarViewPager.setAdapter(new TerminalToolbarViewPager.PageAdapter(this, savedTextInput));
terminalToolbarViewPager.addOnPageChangeListener(new TerminalToolbarViewPager.OnPageChangeListener(this, terminalToolbarViewPager));
}
private void setTerminalToolbarHeight() {
final ViewPager terminalToolbarViewPager = findViewById(R.id.terminal_toolbar_view_pager);
if (terminalToolbarViewPager == null) return;
ViewGroup.LayoutParams layoutParams = terminalToolbarViewPager.getLayoutParams();
layoutParams.height = (int) Math.round(mTerminalToolbarDefaultHeight *
(mProperties.getExtraKeysInfo() == null ? 0 : mProperties.getExtraKeysInfo().getMatrix().length) *
mProperties.getTerminalToolbarHeightScaleFactor());
terminalToolbarViewPager.setLayoutParams(layoutParams);
}
public void toggleTerminalToolbar() {
final ViewPager terminalToolbarViewPager = findViewById(R.id.terminal_toolbar_view_pager);
if (terminalToolbarViewPager == null) return;
final boolean showNow = mPreferences.toogleShowTerminalToolbar();
Logger.showToast(this, (showNow ? getString(R.string.msg_enabling_terminal_toolbar) : getString(R.string.msg_disabling_terminal_toolbar)), true);
terminalToolbarViewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
if (showNow && terminalToolbarViewPager.getCurrentItem() == 1) {
// Focus the text input view if just revealed.
findViewById(R.id.terminal_toolbar_text_input).requestFocus();
}
}
private void saveTerminalToolbarTextInput(Bundle savedInstanceState) {
if (savedInstanceState == null) return;
final EditText textInputView = findViewById(R.id.terminal_toolbar_text_input);
if (textInputView != null) {
String textInput = textInputView.getText().toString();
if (!textInput.isEmpty()) savedInstanceState.putString(ARG_TERMINAL_TOOLBAR_TEXT_INPUT, textInput);
}
}
private void setSettingsButtonView() {
ImageButton settingsButton = findViewById(R.id.settings_button);
settingsButton.setOnClickListener(v -> {
startActivity(new Intent(this, SettingsActivity.class));
});
}
private void setNewSessionButtonView() {
View newSessionButton = findViewById(R.id.new_session_button);
newSessionButton.setOnClickListener(v -> mTermuxTerminalSessionClient.addNewSession(false, null));
newSessionButton.setOnLongClickListener(v -> {
DialogUtils.textInput(TermuxActivity.this, R.string.title_create_named_session, null,
R.string.action_create_named_session_confirm, text -> mTermuxTerminalSessionClient.addNewSession(false, text),
R.string.action_new_session_failsafe, text -> mTermuxTerminalSessionClient.addNewSession(true, text),
-1, null, null);
return true;
});
}
private void setToggleKeyboardView() {
findViewById(R.id.toggle_keyboard_button).setOnClickListener(v -> {
mTermuxTerminalViewClient.onToggleSoftKeyboardRequest();
getDrawer().closeDrawers();
});
findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(v -> {
toggleTerminalToolbar();
return true;
});
}
@SuppressLint("RtlHardcoded")
@Override
public void onBackPressed() {
if (getDrawer().isDrawerOpen(Gravity.LEFT)) {
getDrawer().closeDrawers();
} else {
finishActivityIfNotFinishing();
}
}
public void finishActivityIfNotFinishing() {
// prevent duplicate calls to finish() if called from multiple places
if (!TermuxActivity.this.isFinishing()) {
finish();
}
}
/** Show a toast and dismiss the last one if still visible. */
public void showToast(String text, boolean longDuration) {
if (text == null || text.isEmpty()) return;
if (mLastToast != null) mLastToast.cancel();
mLastToast = Toast.makeText(TermuxActivity.this, text, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
mLastToast.setGravity(Gravity.TOP, 0, 0);
mLastToast.show();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
TerminalSession currentSession = getCurrentSession();
if (currentSession == null) return;
boolean addAutoFillMenu = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillManager autofillManager = getSystemService(AutofillManager.class);
if (autofillManager != null && autofillManager.isEnabled()) {
addAutoFillMenu = true;
}
}
menu.add(Menu.NONE, CONTEXT_MENU_SELECT_URL_ID, Menu.NONE, R.string.action_select_url);
menu.add(Menu.NONE, CONTEXT_MENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.action_share_transcript);
if (addAutoFillMenu) menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_ID, Menu.NONE, R.string.action_autofill_password);
menu.add(Menu.NONE, CONTEXT_MENU_RESET_TERMINAL_ID, Menu.NONE, R.string.action_reset_terminal);
menu.add(Menu.NONE, CONTEXT_MENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.action_kill_process, getCurrentSession().getPid())).setEnabled(currentSession.isRunning());
menu.add(Menu.NONE, CONTEXT_MENU_STYLING_ID, Menu.NONE, R.string.action_style_terminal);
menu.add(Menu.NONE, CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON, Menu.NONE, R.string.action_toggle_keep_screen_on).setCheckable(true).setChecked(mPreferences.shouldKeepScreenOn());
menu.add(Menu.NONE, CONTEXT_MENU_HELP_ID, Menu.NONE, R.string.action_open_help);
menu.add(Menu.NONE, CONTEXT_MENU_SETTINGS_ID, Menu.NONE, R.string.action_open_settings);
menu.add(Menu.NONE, CONTEXT_MENU_REPORT_ID, Menu.NONE, R.string.action_report_issue);
}
/** Hook system menu to show context menu instead. */
@Override
public boolean onCreateOptionsMenu(Menu menu) {
mTerminalView.showContextMenu();
return false;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
TerminalSession session = getCurrentSession();
switch (item.getItemId()) {
case CONTEXT_MENU_SELECT_URL_ID:
mTermuxTerminalViewClient.showUrlSelection();
return true;
case CONTEXT_MENU_SHARE_TRANSCRIPT_ID:
mTermuxTerminalViewClient.shareSessionTranscript();
return true;
case CONTEXT_MENU_AUTOFILL_ID:
requestAutoFill();
return true;
case CONTEXT_MENU_RESET_TERMINAL_ID:
onResetTerminalSession(session);
return true;
case CONTEXT_MENU_KILL_PROCESS_ID:
showKillSessionDialog(session);
return true;
case CONTEXT_MENU_STYLING_ID:
showStylingDialog();
return true;
case CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON:
toggleKeepScreenOn();
return true;
case CONTEXT_MENU_HELP_ID:
startActivity(new Intent(this, HelpActivity.class));
return true;
case CONTEXT_MENU_SETTINGS_ID:
startActivity(new Intent(this, SettingsActivity.class));
return true;
case CONTEXT_MENU_REPORT_ID:
mTermuxTerminalViewClient.reportIssueFromTranscript();
return true;
default:
return super.onContextItemSelected(item);
}
}
private void showKillSessionDialog(TerminalSession session) {
if (session == null) return;
final AlertDialog.Builder b = new AlertDialog.Builder(this);
b.setIcon(android.R.drawable.ic_dialog_alert);
b.setMessage(R.string.title_confirm_kill_process);
b.setPositiveButton(android.R.string.yes, (dialog, id) -> {
dialog.dismiss();
session.finishIfRunning();
});
b.setNegativeButton(android.R.string.no, null);
b.show();
}
private void onResetTerminalSession(TerminalSession session) {
if (session != null) {
session.reset();
showToast(getResources().getString(R.string.msg_terminal_reset), true);
if (mTermuxTerminalSessionClient != null)
mTermuxTerminalSessionClient.onResetTerminalSession();
}
}
private void showStylingDialog() {
Intent stylingIntent = new Intent();
stylingIntent.setClassName(TermuxConstants.TERMUX_STYLING_PACKAGE_NAME, TermuxConstants.TERMUX_STYLING.TERMUX_STYLING_ACTIVITY_NAME);
try {
startActivity(stylingIntent);
} catch (ActivityNotFoundException | IllegalArgumentException e) {
// The startActivity() call is not documented to throw IllegalArgumentException.
// However, crash reporting shows that it sometimes does, so catch it here.
new AlertDialog.Builder(this).setMessage(getString(R.string.error_styling_not_installed))
.setPositiveButton(R.string.action_styling_install, (dialog, which) -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(TermuxConstants.TERMUX_STYLING_FDROID_PACKAGE_URL)))).setNegativeButton(android.R.string.cancel, null).show();
}
}
private void toggleKeepScreenOn() {
if (mTerminalView.getKeepScreenOn()) {
mTerminalView.setKeepScreenOn(false);
mPreferences.setKeepScreenOn(false);
} else {
mTerminalView.setKeepScreenOn(true);
mPreferences.setKeepScreenOn(true);
}
}
private void requestAutoFill() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillManager autofillManager = getSystemService(AutofillManager.class);
if (autofillManager != null && autofillManager.isEnabled()) {
autofillManager.requestAutofill(mTerminalView);
}
}
}
/**
* For processes to access shared internal storage (/sdcard) we need this permission.
*/
public boolean ensureStoragePermissionGranted() {
if (PermissionUtils.checkPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
return true;
} else {
Logger.logInfo(LOG_TAG, "Storage permission not granted, requesting permission.");
PermissionUtils.requestPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE, PermissionUtils.REQUEST_GRANT_STORAGE_PERMISSION);
return false;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionUtils.REQUEST_GRANT_STORAGE_PERMISSION && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Logger.logInfo(LOG_TAG, "Storage permission granted by user on request.");
TermuxInstaller.setupStorageSymlinks(this);
} else {
Logger.logInfo(LOG_TAG, "Storage permission denied by user on request.");
}
}
public int getNavBarHeight() {
return mNavBarHeight;
}
public TermuxActivityRootView getTermuxActivityRootView() {
return mTermuxActivityRootView;
}
public View getTermuxActivityBottomSpaceView() {
return mTermuxActivityBottomSpaceView;
}
public ExtraKeysView getExtraKeysView() {
return mExtraKeysView;
}
public void setExtraKeysView(ExtraKeysView extraKeysView) {
mExtraKeysView = extraKeysView;
}
public DrawerLayout getDrawer() {
return (DrawerLayout) findViewById(R.id.drawer_layout);
}
public void termuxSessionListNotifyUpdated() {
mTermuxSessionListViewController.notifyDataSetChanged();
}
public boolean isVisible() {
return mIsVisible;
}
public boolean isOnResumeAfterOnCreate() {
return isOnResumeAfterOnCreate;
}
public TermuxService getTermuxService() {
return mTermuxService;
}
public TerminalView getTerminalView() {
return mTerminalView;
}
public TermuxTerminalViewClient getTermuxTerminalViewClient() {
return mTermuxTerminalViewClient;
}
public TermuxTerminalSessionClient getTermuxTerminalSessionClient() {
return mTermuxTerminalSessionClient;
}
@Nullable
public TerminalSession getCurrentSession() {
if (mTerminalView != null)
return mTerminalView.getCurrentSession();
else
return null;
}
public TermuxAppSharedPreferences getPreferences() {
return mPreferences;
}
public TermuxAppSharedProperties getProperties() {
return mProperties;
}
public static void updateTermuxActivityStyling(Context context) {
// Make sure that terminal styling is always applied.
Intent stylingIntent = new Intent(TERMUX_ACTIVITY.ACTION_RELOAD_STYLE);
context.sendBroadcast(stylingIntent);
}
private void registerTermuxActivityBroadcastReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TERMUX_ACTIVITY.ACTION_REQUEST_PERMISSIONS);
intentFilter.addAction(TERMUX_ACTIVITY.ACTION_RELOAD_STYLE);
registerReceiver(mTermuxActivityBroadcastReceiver, intentFilter);
}
private void unregisterTermuxActivityBroadcastReceiever() {
unregisterReceiver(mTermuxActivityBroadcastReceiver);
}
private void fixTermuxActivityBroadcastReceieverIntent(Intent intent) {
if (intent == null) return;
String extraReloadStyle = intent.getStringExtra(TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE);
if ("storage".equals(extraReloadStyle)) {
intent.removeExtra(TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE);
intent.setAction(TERMUX_ACTIVITY.ACTION_REQUEST_PERMISSIONS);
}
}
class TermuxActivityBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) return;
if (mIsVisible) {
fixTermuxActivityBroadcastReceieverIntent(intent);
switch (intent.getAction()) {
case TERMUX_ACTIVITY.ACTION_REQUEST_PERMISSIONS:
Logger.logDebug(LOG_TAG, "Received intent to request storage permissions");
if (ensureStoragePermissionGranted())
TermuxInstaller.setupStorageSymlinks(TermuxActivity.this);
return;
case TERMUX_ACTIVITY.ACTION_RELOAD_STYLE:
Logger.logDebug(LOG_TAG, "Received intent to reload styling");
reloadActivityStyling();
return;
default:
}
}
}
}
private void reloadActivityStyling() {
if (mProperties!= null) {
mProperties.loadTermuxPropertiesFromDisk();
if (mExtraKeysView != null) {
mExtraKeysView.reload(mProperties.getExtraKeysInfo());
}
}
setTerminalToolbarHeight();
if (mTermuxTerminalSessionClient != null)
mTermuxTerminalSessionClient.onReload();
if (mTermuxTerminalViewClient != null)
mTermuxTerminalViewClient.onReload();
if (mTermuxService != null)
mTermuxService.setTerminalTranscriptRows();
// To change the activity and drawer theme, activity needs to be recreated.
// But this will destroy the activity, and will call the onCreate() again.
// We need to investigate if enabling this is wise, since all stored variables and
// views will be destroyed and bindService() will be called again. Extra keys input
// text will we restored since that has already been implemented. Terminal sessions
// and transcripts are also already preserved. Theme does change properly too.
// TermuxActivity.this.recreate();
}
public static void startTermuxActivity(@NonNull final Context context) {
context.startActivity(newInstance(context));
}
public static Intent newInstance(@NonNull final Context context) {
Intent intent = new Intent(context, TermuxActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
}