diff --git a/app/build.gradle b/app/build.gradle index c1f0c6df..52c51d4b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -87,6 +87,8 @@ android { } dependencies { + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.preference:preference:1.1.1' testImplementation 'junit:junit:4.13.1' testImplementation 'org.robolectric:robolectric:4.4' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0a2e70a6..346f6d3b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,17 +1,23 @@ + + android:sharedUserLabel="@string/shared_user_label"> - - + + - @@ -20,65 +26,95 @@ - - + + + android:banner="@drawable/banner" + android:extractNativeLibs="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/application_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="false" + android:theme="@style/Theme.Termux"> - - + + + android:windowSoftInputMode="adjustResize|stateAlwaysVisible"> + + - + + + + + + + + + + + + + + android:theme="@android:style/Theme.Material.Light.DarkActionBar" /> + + + android:taskAffinity="${TERMUX_PACKAGE_NAME}.filereceiver"> + - + + + @@ -89,8 +125,10 @@ - + + + @@ -99,23 +137,11 @@ - - - - - - - - - - @@ -125,11 +151,10 @@ - + android:permission="${TERMUX_PACKAGE_NAME}.permission.RUN_COMMAND"> @@ -137,13 +162,19 @@ - - - + + + + diff --git a/app/src/main/java/com/termux/app/BackgroundJob.java b/app/src/main/java/com/termux/app/BackgroundJob.java index 3f502d16..61e32317 100644 --- a/app/src/main/java/com/termux/app/BackgroundJob.java +++ b/app/src/main/java/com/termux/app/BackgroundJob.java @@ -4,9 +4,9 @@ import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; -import android.util.Log; import com.termux.BuildConfig; +import com.termux.app.utils.Logger; import java.io.BufferedReader; import java.io.File; @@ -26,10 +26,10 @@ import java.util.List; */ public final class BackgroundJob { - private static final String LOG_TAG = "termux-task"; - final Process mProcess; + private static final String LOG_TAG = "BackgroundJob"; + public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service){ this(cwd, fileToExecute, args, service, null); } @@ -47,7 +47,7 @@ public final class BackgroundJob { } catch (IOException e) { mProcess = null; // TODO: Visible error message? - Log.e(LOG_TAG, "Failed running background job: " + processDescription, e); + Logger.logStackTraceWithMessage(LOG_TAG, "Failed running background job: " + processDescription, e); return; } @@ -67,7 +67,7 @@ public final class BackgroundJob { // FIXME: Long lines. while ((line = reader.readLine()) != null) { errResult.append(line).append('\n'); - Log.i(LOG_TAG, "[" + pid + "] stderr: " + line); + Logger.logDebug(LOG_TAG, "[" + pid + "] stderr: " + line); } } catch (IOException e) { // Ignore. @@ -79,7 +79,7 @@ public final class BackgroundJob { new Thread() { @Override public void run() { - Log.i(LOG_TAG, "[" + pid + "] starting: " + processDescription); + Logger.logDebug(LOG_TAG, "[" + pid + "] starting: " + processDescription); InputStream stdout = mProcess.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8)); @@ -87,20 +87,20 @@ public final class BackgroundJob { try { // FIXME: Long lines. while ((line = reader.readLine()) != null) { - Log.i(LOG_TAG, "[" + pid + "] stdout: " + line); + Logger.logDebug(LOG_TAG, "[" + pid + "] stdout: " + line); outResult.append(line).append('\n'); } } catch (IOException e) { - Log.e(LOG_TAG, "Error reading output", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error reading output", e); } try { int exitCode = mProcess.waitFor(); service.onBackgroundJobExited(BackgroundJob.this); if (exitCode == 0) { - Log.i(LOG_TAG, "[" + pid + "] exited normally"); + Logger.logDebug(LOG_TAG, "[" + pid + "] exited normally"); } else { - Log.w(LOG_TAG, "[" + pid + "] exited with code: " + exitCode); + Logger.logDebug(LOG_TAG, "[" + pid + "] exited with code: " + exitCode); } result.putString("stdout", outResult.toString()); diff --git a/app/src/main/java/com/termux/app/RunCommandService.java b/app/src/main/java/com/termux/app/RunCommandService.java index f186e5b2..8745d31a 100644 --- a/app/src/main/java/com/termux/app/RunCommandService.java +++ b/app/src/main/java/com/termux/app/RunCommandService.java @@ -10,13 +10,13 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.IBinder; -import android.util.Log; import com.termux.R; import com.termux.app.TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE; import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_SERVICE; import com.termux.app.settings.properties.TermuxPropertyConstants; import com.termux.app.settings.properties.TermuxSharedProperties; +import com.termux.app.utils.Logger; import java.io.File; import java.io.FileInputStream; @@ -84,6 +84,8 @@ public class RunCommandService extends Service { private static final String NOTIFICATION_CHANNEL_ID = "termux_run_command_notification_channel"; private static final int NOTIFICATION_ID = 1338; + private static final String LOG_TAG = "RunCommandService"; + class LocalBinder extends Binder { public final RunCommandService service = RunCommandService.this; } @@ -107,13 +109,13 @@ public class RunCommandService extends Service { // If wrong action passed, then just return if (!RUN_COMMAND_SERVICE.ACTION_RUN_COMMAND.equals(intent.getAction())) { - Log.e("termux", "Unexpected intent action to RunCommandService: " + intent.getAction()); + Logger.logError(LOG_TAG, "Unexpected intent action to RunCommandService: " + intent.getAction()); return Service.START_NOT_STICKY; } // If allow-external-apps property is not set to "true" if (!TermuxSharedProperties.isPropertyValueTrue(this, TermuxPropertyConstants.getTermuxPropertiesFile(), TermuxConstants.PROP_ALLOW_EXTERNAL_APPS)) { - Log.e("termux", "RunCommandService requires allow-external-apps property to be set to \"true\" in \"" + TermuxConstants.TERMUX_PROPERTIES_PRIMARY_FILE_PATH + "\" file"); + Logger.logError(LOG_TAG, "RunCommandService requires allow-external-apps property to be set to \"true\" in \"" + TermuxConstants.TERMUX_PROPERTIES_PRIMARY_FILE_PATH + "\" file"); return Service.START_NOT_STICKY; } @@ -155,7 +157,7 @@ public class RunCommandService extends Service { private Notification buildNotification() { Notification.Builder builder = new Notification.Builder(this); - builder.setContentTitle(getText(R.string.application_name) + " Run Command"); + builder.setContentTitle(TermuxConstants.TERMUX_APP_NAME + " Run Command"); builder.setSmallIcon(R.drawable.ic_service_notification); // Use a low priority: @@ -177,7 +179,7 @@ public class RunCommandService extends Service { private void setupNotificationChannel() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; - String channelName = "Termux Run Command"; + String channelName = TermuxConstants.TERMUX_APP_NAME + " Run Command"; int importance = NotificationManager.IMPORTANCE_LOW; NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, importance); diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index ed12ec61..8b75f7ee 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -28,7 +28,6 @@ import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.style.StyleSpan; -import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; @@ -55,7 +54,7 @@ import com.termux.app.terminal.extrakeys.ExtraKeysView; import com.termux.app.terminal.FullScreenWorkAround; import com.termux.app.settings.properties.TermuxPropertyConstants; import com.termux.app.settings.properties.TermuxSharedProperties; -import com.termux.terminal.EmulatorDebug; +import com.termux.app.utils.Logger; import com.termux.terminal.TerminalColors; import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession.SessionChangedCallback; @@ -100,6 +99,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection private static final int CONTEXTMENU_HELP_ID = 8; private static final int CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON = 9; private static final int CONTEXTMENU_AUTOFILL_ID = 10; + private static final int CONTEXTMENU_SETTINGS_ID = 11; private static final int MAX_SESSIONS = 8; @@ -145,12 +145,14 @@ public final class TermuxActivity extends Activity implements ServiceConnection .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build(); int mBellSoundId; + private static final String LOG_TAG = "TermuxActivity"; + private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mIsVisible) { String whatToReload = intent.getStringExtra(TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE); - Log.d("termux", "Reloading termux style for: " + whatToReload); + Logger.logDebug(LOG_TAG, "Reloading termux style for: " + whatToReload); if ("storage".equals(whatToReload)) { if (ensureStoragePermissionGranted()) TermuxInstaller.setupStorageSymlinks(TermuxActivity.this); @@ -190,7 +192,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE; mTerminalView.setTypeface(newTypeface); } catch (Exception e) { - Log.e(EmulatorDebug.LOG_TAG, "Error in checkForFontAndColors()", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error in checkForFontAndColors()", e); } } @@ -245,10 +247,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection } mTerminalView = findViewById(R.id.terminal_view); - mTerminalView.setOnKeyListener(new TermuxViewClient(this)); + + mTerminalView.setTerminalViewClient(new TermuxViewClient(this)); mTerminalView.setTextSize(mPreferences.getFontSize()); mTerminalView.setKeepScreenOn(mPreferences.getKeepScreenOn()); + mTerminalView.setIsTerminalViewKeyLoggingEnabled(mPreferences.getTerminalViewKeyLoggingEnabled()); mTerminalView.requestFocus(); final ViewPager viewPager = findViewById(R.id.viewpager); @@ -627,6 +631,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection registerReceiver(mBroadcastReceiever, new IntentFilter(TERMUX_ACTIVITY.ACTION_RELOAD_STYLE)); + mTerminalView.setIsTerminalViewKeyLoggingEnabled(mPreferences.getTerminalViewKeyLoggingEnabled()); + // The current terminal session may have changed while being away, force // a refresh of the displayed terminal: mTerminalView.onScreenUpdated(); @@ -742,6 +748,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal); menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON, Menu.NONE, R.string.toggle_keep_screen_on).setCheckable(true).setChecked(mPreferences.getKeepScreenOn()); menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help); + menu.add(Menu.NONE, CONTEXTMENU_SETTINGS_ID, Menu.NONE, R.string.settings); } /** Hook system menu to show context menu instead. */ @@ -948,6 +955,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection case CONTEXTMENU_HELP_ID: startActivity(new Intent(this, TermuxHelpActivity.class)); return true; + case CONTEXTMENU_SETTINGS_ID: + startActivity(new Intent(this, TermuxSettingsActivity.class)); + return true; case CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON: { if(mTerminalView.getKeepScreenOn()) { mTerminalView.setKeepScreenOn(false); @@ -1036,6 +1046,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection } } + public boolean isVisible() { + return mIsVisible; + } + public TermuxService getTermService() { return mTermService; } @@ -1047,8 +1061,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection public ExtraKeysView getExtraKeysView() { return mExtraKeysView; } + public TermuxSharedProperties getProperties() { return mProperties; } + public TermuxSharedPreferences getPreferences() { + return mPreferences; + } + } diff --git a/app/src/main/java/com/termux/app/TermuxApplication.java b/app/src/main/java/com/termux/app/TermuxApplication.java new file mode 100644 index 00000000..94c4dbcc --- /dev/null +++ b/app/src/main/java/com/termux/app/TermuxApplication.java @@ -0,0 +1,23 @@ +package com.termux.app; + +import android.app.Application; + +import com.termux.app.settings.preferences.TermuxSharedPreferences; +import com.termux.app.utils.Logger; + + +public class TermuxApplication extends Application { + public void onCreate() { + super.onCreate(); + + updateLogLevel(); + } + + private void updateLogLevel() { + // Load the log level from shared preferences and set it to the {@link Loggger.CURRENT_LOG_LEVEL} + TermuxSharedPreferences preferences = new TermuxSharedPreferences(getApplicationContext()); + preferences.setLogLevel(null, preferences.getLogLevel()); + Logger.logDebug("Starting Application"); + } +} + diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index c5bf3d17..6b218a4f 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -7,12 +7,11 @@ import android.content.Context; import android.os.Environment; import android.os.UserManager; import android.system.Os; -import android.util.Log; import android.util.Pair; import android.view.WindowManager; import com.termux.R; -import com.termux.terminal.EmulatorDebug; +import com.termux.app.utils.Logger; import java.io.BufferedReader; import java.io.ByteArrayInputStream; @@ -46,6 +45,8 @@ import java.util.zip.ZipInputStream; */ final class TermuxInstaller { + private static final String LOG_TAG = "TermuxInstaller"; + /** Performs setup if necessary. */ static void setupIfNeeded(final Activity activity, final Runnable whenDone) { // Termux can only be run as the primary user (device owner) since only that @@ -130,7 +131,7 @@ final class TermuxInstaller { activity.runOnUiThread(whenDone); } catch (final Exception e) { - Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Bootstrap error", e); activity.runOnUiThread(() -> { try { new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body) @@ -200,13 +201,13 @@ final class TermuxInstaller { try { deleteFolder(storageDir); } catch (IOException e) { - Log.e(LOG_TAG, "Could not delete old $HOME/storage, " + e.getMessage()); + Logger.logError(LOG_TAG, "Could not delete old $HOME/storage, " + e.getMessage()); return; } } if (!storageDir.mkdirs()) { - Log.e(LOG_TAG, "Unable to mkdirs() for $HOME/storage"); + Logger.logError(LOG_TAG, "Unable to mkdirs() for $HOME/storage"); return; } @@ -238,7 +239,7 @@ final class TermuxInstaller { } } } catch (Exception e) { - Log.e(LOG_TAG, "Error setting up link", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error setting up link", e); } } }.start(); diff --git a/app/src/main/java/com/termux/app/TermuxOpenReceiver.java b/app/src/main/java/com/termux/app/TermuxOpenReceiver.java index 960ab8e5..92ce70f8 100644 --- a/app/src/main/java/com/termux/app/TermuxOpenReceiver.java +++ b/app/src/main/java/com/termux/app/TermuxOpenReceiver.java @@ -11,10 +11,9 @@ import android.net.Uri; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; -import android.util.Log; import android.webkit.MimeTypeMap; -import com.termux.terminal.EmulatorDebug; +import com.termux.app.utils.Logger; import java.io.File; import java.io.FileNotFoundException; @@ -24,11 +23,13 @@ import androidx.annotation.NonNull; public class TermuxOpenReceiver extends BroadcastReceiver { + private static final String LOG_TAG = "TermuxOpenReceiver"; + @Override public void onReceive(Context context, Intent intent) { final Uri data = intent.getData(); if (data == null) { - Log.e(EmulatorDebug.LOG_TAG, "termux-open: Called without intent data"); + Logger.logError(LOG_TAG, "termux-open: Called without intent data"); return; } @@ -42,7 +43,7 @@ public class TermuxOpenReceiver extends BroadcastReceiver { // Ok. break; default: - Log.e(EmulatorDebug.LOG_TAG, "Invalid action '" + intentAction + "', using 'view'"); + Logger.logError(LOG_TAG, "Invalid action '" + intentAction + "', using 'view'"); break; } @@ -59,14 +60,14 @@ public class TermuxOpenReceiver extends BroadcastReceiver { try { context.startActivity(urlIntent); } catch (ActivityNotFoundException e) { - Log.e(EmulatorDebug.LOG_TAG, "termux-open: No app handles the url " + data); + Logger.logError(LOG_TAG, "termux-open: No app handles the url " + data); } return; } final File fileToShare = new File(filePath); if (!(fileToShare.isFile() && fileToShare.canRead())) { - Log.e(EmulatorDebug.LOG_TAG, "termux-open: Not a readable file: '" + fileToShare.getAbsolutePath() + "'"); + Logger.logError(LOG_TAG, "termux-open: Not a readable file: '" + fileToShare.getAbsolutePath() + "'"); return; } @@ -103,7 +104,7 @@ public class TermuxOpenReceiver extends BroadcastReceiver { try { context.startActivity(sendIntent); } catch (ActivityNotFoundException e) { - Log.e(EmulatorDebug.LOG_TAG, "termux-open: No app handles the url " + data); + Logger.logError(LOG_TAG, "termux-open: No app handles the url " + data); } } diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index 3301b30a..cc624d60 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -18,14 +18,13 @@ import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.provider.Settings; -import android.util.Log; import android.widget.ArrayAdapter; import com.termux.R; import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY; import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_SERVICE; import com.termux.app.settings.preferences.TermuxSharedPreferences; -import com.termux.terminal.EmulatorDebug; +import com.termux.app.utils.Logger; import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession.SessionChangedCallback; @@ -50,6 +49,8 @@ public final class TermuxService extends Service implements SessionChangedCallba private static final String NOTIFICATION_CHANNEL_ID = "termux_notification_channel"; private static final int NOTIFICATION_ID = 1337; + private static final String LOG_TAG = "TermuxService"; + /** This service is only bound from inside the same process and never uses IPC. */ class LocalBinder extends Binder { public final TermuxService service = TermuxService.this; @@ -91,12 +92,12 @@ public final class TermuxService extends Service implements SessionChangedCallba } 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, EmulatorDebug.LOG_TAG + ":service-wakelock"); + 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, EmulatorDebug.LOG_TAG); + mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TermuxConstants.TERMUX_APP_NAME.toLowerCase()); mWifiLock.acquire(); String packageName = getPackageName(); @@ -109,7 +110,7 @@ public final class TermuxService extends Service implements SessionChangedCallba try { startActivity(whitelist); } catch (ActivityNotFoundException e) { - Log.e(EmulatorDebug.LOG_TAG, "Failed to call ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to call ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", e); } } @@ -156,7 +157,7 @@ public final class TermuxService extends Service implements SessionChangedCallba startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } } else if (action != null) { - Log.e(EmulatorDebug.LOG_TAG, "Unknown TermuxService action: '" + action + "'"); + 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 @@ -246,7 +247,7 @@ public final class TermuxService extends Service implements SessionChangedCallba try { TermuxInstaller.deleteFolder(termuxTmpDir.getCanonicalFile()); } catch (Exception e) { - Log.e(EmulatorDebug.LOG_TAG, "Error while removing file at " + termuxTmpDir.getAbsolutePath(), e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error while removing file at " + termuxTmpDir.getAbsolutePath(), e); } termuxTmpDir.mkdirs(); @@ -367,8 +368,8 @@ public final class TermuxService extends Service implements SessionChangedCallba private void setupNotificationChannel() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; - String channelName = "Termux"; - String channelDescription = "Notifications from Termux"; + String channelName = TermuxConstants.TERMUX_APP_NAME; + String channelDescription = "Notifications from " + TermuxConstants.TERMUX_APP_NAME; int importance = NotificationManager.IMPORTANCE_LOW; NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,importance); @@ -376,4 +377,8 @@ public final class TermuxService extends Service implements SessionChangedCallba NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.createNotificationChannel(channel); } + + public boolean wantsToStop() { + return mWantsToStop; + } } diff --git a/app/src/main/java/com/termux/app/TermuxSettingsActivity.java b/app/src/main/java/com/termux/app/TermuxSettingsActivity.java new file mode 100644 index 00000000..74604c96 --- /dev/null +++ b/app/src/main/java/com/termux/app/TermuxSettingsActivity.java @@ -0,0 +1,43 @@ +package com.termux.app; + +import android.os.Bundle; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.PreferenceFragmentCompat; + +import com.termux.R; + +public class TermuxSettingsActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.settings_activity); + if (savedInstanceState == null) { + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.settings, new RootPreferencesFragment()) + .commit(); + } + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowHomeEnabled(true); + } + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + public static class RootPreferencesFragment extends PreferenceFragmentCompat { + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.root_preferences, rootKey); + } + } + +} diff --git a/app/src/main/java/com/termux/app/settings/DebuggingPreferencesFragment.java b/app/src/main/java/com/termux/app/settings/DebuggingPreferencesFragment.java new file mode 100644 index 00000000..c976c30e --- /dev/null +++ b/app/src/main/java/com/termux/app/settings/DebuggingPreferencesFragment.java @@ -0,0 +1,122 @@ +package com.termux.app.settings; + +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.preference.ListPreference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceDataStore; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +import com.termux.R; +import com.termux.app.settings.preferences.TermuxPreferenceConstants; +import com.termux.app.settings.preferences.TermuxSharedPreferences; +import com.termux.app.utils.Logger; + +public class DebuggingPreferencesFragment extends PreferenceFragmentCompat { + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + PreferenceManager preferenceManager = getPreferenceManager(); + preferenceManager.setPreferenceDataStore(DebuggingPreferencesDataStore.getInstance(getContext())); + + setPreferencesFromResource(R.xml.debugging_preferences, rootKey); + + PreferenceCategory loggingCategory = findPreference("logging"); + + if (loggingCategory != null) { + final ListPreference logLevelListPreference = setLogLevelListPreferenceData(findPreference("log_level"), getActivity()); + loggingCategory.addPreference(logLevelListPreference); + } + } + + protected ListPreference setLogLevelListPreferenceData(ListPreference logLevelListPreference, Context context) { + if(logLevelListPreference == null) + logLevelListPreference = new ListPreference(context); + + CharSequence[] logLevels = Logger.getLogLevelsArray(); + CharSequence[] logLevelLabels = Logger.getLogLevelLabelsArray(context, logLevels, true); + + logLevelListPreference.setEntryValues(logLevels); + logLevelListPreference.setEntries(logLevelLabels); + + logLevelListPreference.setKey(TermuxPreferenceConstants.KEY_LOG_LEVEL); + logLevelListPreference.setValue(String.valueOf(Logger.getLogLevel())); + logLevelListPreference.setDefaultValue(Logger.getLogLevel()); + + return logLevelListPreference; + } + +} + +class DebuggingPreferencesDataStore extends PreferenceDataStore { + + private final Context mContext; + private final TermuxSharedPreferences mPreferences; + + private static DebuggingPreferencesDataStore mInstance; + + private DebuggingPreferencesDataStore(Context context) { + mContext = context; + mPreferences = new TermuxSharedPreferences(context); + } + + public static synchronized DebuggingPreferencesDataStore getInstance(Context context) { + if (mInstance == null) { + mInstance = new DebuggingPreferencesDataStore(context.getApplicationContext()); + } + return mInstance; + } + + @Override + @Nullable + public String getString(String key, @Nullable String defValue) { + if(key == null) return null; + + switch (key) { + case "log_level": + return String.valueOf(mPreferences.getLogLevel()); + default: + return null; + } + } + + @Override + public void putString(String key, @Nullable String value) { + if(key == null) return; + + switch (key) { + case "log_level": + if (value != null) { + mPreferences.setLogLevel(mContext, Integer.parseInt(value)); + } + break; + default: + break; + } + } + + @Override + public void putBoolean(String key, boolean value) { + if(key == null) return; + + switch (key) { + case "terminal_view_key_logging_enabled": + mPreferences.setTerminalViewKeyLoggingEnabled(value); + break; + default: + break; + } + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + switch (key) { + case "terminal_view_key_logging_enabled": + return mPreferences.getTerminalViewKeyLoggingEnabled(); + default: + return false; + } + } +} diff --git a/app/src/main/java/com/termux/app/settings/preferences/TermuxPreferenceConstants.java b/app/src/main/java/com/termux/app/settings/preferences/TermuxPreferenceConstants.java index f9ba174d..a12aa954 100644 --- a/app/src/main/java/com/termux/app/settings/preferences/TermuxPreferenceConstants.java +++ b/app/src/main/java/com/termux/app/settings/preferences/TermuxPreferenceConstants.java @@ -3,13 +3,14 @@ package com.termux.app.settings.preferences; import com.termux.app.TermuxConstants; /* - * Version: v0.1.0 + * Version: v0.2.0 * * Changelog * * - 0.1.0 (2021-03-12) * - Initial Release. - * + * - 0.2.0 (2021-03-13) + * - Added `KEY_LOG_LEVEL` and `KEY_TERMINAL_VIEW_LOGGING_ENABLED` */ /** @@ -47,4 +48,13 @@ public final class TermuxPreferenceConstants { + /** Defines the key for current termux log level */ + public static final String KEY_LOG_LEVEL = "log_level"; + + + + /** Defines the key for whether termux terminal view key logging is enabled or not */ + public static final String KEY_TERMINAL_VIEW_KEY_LOGGING_ENABLED = "terminal_view_key_logging_enabled"; + public static final boolean DEFAULT_VALUE_TERMINAL_VIEW_KEY_LOGGING_ENABLED = false; + } diff --git a/app/src/main/java/com/termux/app/settings/preferences/TermuxSharedPreferences.java b/app/src/main/java/com/termux/app/settings/preferences/TermuxSharedPreferences.java index 65fe9679..1278dd3e 100644 --- a/app/src/main/java/com/termux/app/settings/preferences/TermuxSharedPreferences.java +++ b/app/src/main/java/com/termux/app/settings/preferences/TermuxSharedPreferences.java @@ -2,11 +2,11 @@ package com.termux.app.settings.preferences; import android.content.Context; import android.content.SharedPreferences; -import android.util.Log; import android.util.TypedValue; import com.termux.app.TermuxConstants; -import com.termux.terminal.EmulatorDebug; +import com.termux.app.utils.Logger; +import com.termux.app.utils.TermuxUtils; import javax.annotation.Nonnull; @@ -21,17 +21,7 @@ public class TermuxSharedPreferences { private int DEFAULT_FONTSIZE; public TermuxSharedPreferences(@Nonnull Context context) { - Context mTempContext; - - try { - mTempContext = context.createPackageContext(TermuxConstants.TERMUX_PACKAGE_NAME, Context.CONTEXT_RESTRICTED); - } catch (Exception e) { - Log.e(EmulatorDebug.LOG_TAG, "Failed to get \"" + TermuxConstants.TERMUX_PACKAGE_NAME + "\" package context", e); - Log.e(EmulatorDebug.LOG_TAG, "Force using current context"); - mTempContext = context; - } - - mContext = mTempContext; + mContext = TermuxUtils.getTermuxPackageContext(context); mSharedPreferences = getSharedPreferences(mContext); setFontVariables(context); @@ -135,4 +125,31 @@ public class TermuxSharedPreferences { mSharedPreferences.edit().putString(TermuxPreferenceConstants.KEY_CURRENT_SESSION, value).apply(); } + + + public int getLogLevel() { + try { + return mSharedPreferences.getInt(TermuxPreferenceConstants.KEY_LOG_LEVEL, Logger.DEFAULT_LOG_LEVEL); + } + catch (Exception e) { + Logger.logStackTraceWithMessage("Error getting \"" + TermuxPreferenceConstants.KEY_LOG_LEVEL + "\" from shared preferences", e); + return Logger.DEFAULT_LOG_LEVEL; + } + } + + public void setLogLevel(Context context, int logLevel) { + logLevel = Logger.setLogLevel(context, logLevel); + mSharedPreferences.edit().putInt(TermuxPreferenceConstants.KEY_LOG_LEVEL, logLevel).apply(); + } + + + + public boolean getTerminalViewKeyLoggingEnabled() { + return mSharedPreferences.getBoolean(TermuxPreferenceConstants.KEY_TERMINAL_VIEW_KEY_LOGGING_ENABLED, TermuxPreferenceConstants.DEFAULT_VALUE_TERMINAL_VIEW_KEY_LOGGING_ENABLED); + } + + public void setTerminalViewKeyLoggingEnabled(boolean value) { + mSharedPreferences.edit().putBoolean(TermuxPreferenceConstants.KEY_TERMINAL_VIEW_KEY_LOGGING_ENABLED, value).apply(); + } + } diff --git a/app/src/main/java/com/termux/app/settings/properties/SharedProperties.java b/app/src/main/java/com/termux/app/settings/properties/SharedProperties.java index db6eb637..bf47fa00 100644 --- a/app/src/main/java/com/termux/app/settings/properties/SharedProperties.java +++ b/app/src/main/java/com/termux/app/settings/properties/SharedProperties.java @@ -1,10 +1,10 @@ package com.termux.app.settings.properties; import android.content.Context; -import android.util.Log; import android.widget.Toast; import com.google.common.primitives.Primitives; +import com.termux.app.utils.Logger; import java.io.File; import java.io.FileInputStream; @@ -57,6 +57,8 @@ public class SharedProperties { private final Object mLock = new Object(); + private static final String LOG_TAG = "SharedProperties"; + /** * Constructor for the SharedProperties class. * @@ -97,7 +99,7 @@ public class SharedProperties { Object internalValue; for (String key : mPropertiesList) { value = properties.getProperty(key); // value will be null if key does not exist in propertiesFile - Log.d("termux", key + " : " + value); + Logger.logDebug(LOG_TAG, key + " : " + value); // Call the {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)} // interface method to get the internal value to store in the {@link #mMap}. @@ -129,13 +131,13 @@ public class SharedProperties { public static boolean putToMap(HashMap map, String key, Object value) { if (map == null) { - Log.e("termux", "Map passed to SharedProperties.putToProperties() is null"); + Logger.logError(LOG_TAG, "Map passed to SharedProperties.putToProperties() is null"); return false; } // null keys are not allowed to be stored in mMap if (key == null) { - Log.e("termux", "Cannot put a null key into properties map"); + Logger.logError(LOG_TAG, "Cannot put a null key into properties map"); return false; } @@ -153,7 +155,7 @@ public class SharedProperties { map.put(key, value); return true; } else { - Log.e("termux", "Cannot put a non-primitive value for the key \"" + key + "\" into properties map"); + Logger.logError(LOG_TAG, "Cannot put a non-primitive value for the key \"" + key + "\" into properties map"); return false; } } @@ -172,13 +174,13 @@ public class SharedProperties { public static boolean putToProperties(Properties properties, String key, String value) { if (properties == null) { - Log.e("termux", "Properties passed to SharedProperties.putToProperties() is null"); + Logger.logError(LOG_TAG, "Properties passed to SharedProperties.putToProperties() is null"); return false; } // null keys are not allowed to be stored in mMap if (key == null) { - Log.e("termux", "Cannot put a null key into properties"); + Logger.logError(LOG_TAG, "Cannot put a null key into properties"); return false; } @@ -206,19 +208,19 @@ public class SharedProperties { Properties properties = new Properties(); if (propertiesFile == null) { - Log.e("termux", "Not loading properties since file is null"); + Logger.logError(LOG_TAG, "Not loading properties since file is null"); return properties; } try { try (FileInputStream in = new FileInputStream(propertiesFile)) { - Log.v("termux", "Loading properties from \"" + propertiesFile.getAbsolutePath() + "\" file"); + Logger.logVerbose(LOG_TAG, "Loading properties from \"" + propertiesFile.getAbsolutePath() + "\" file"); properties.load(new InputStreamReader(in, StandardCharsets.UTF_8)); } } catch (Exception e) { if(context != null) Toast.makeText(context, "Could not open properties file \"" + propertiesFile.getAbsolutePath() + "\": " + e.getMessage(), Toast.LENGTH_LONG).show(); - Log.e("termux", "Error loading properties file \"" + propertiesFile.getAbsolutePath() + "\"", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error loading properties file \"" + propertiesFile.getAbsolutePath() + "\"", e); return null; } diff --git a/app/src/main/java/com/termux/app/settings/properties/TermuxPropertyConstants.java b/app/src/main/java/com/termux/app/settings/properties/TermuxPropertyConstants.java index a68e0466..6e3dd673 100644 --- a/app/src/main/java/com/termux/app/settings/properties/TermuxPropertyConstants.java +++ b/app/src/main/java/com/termux/app/settings/properties/TermuxPropertyConstants.java @@ -1,9 +1,8 @@ package com.termux.app.settings.properties; -import android.util.Log; - import com.google.common.collect.ImmutableBiMap; import com.termux.app.TermuxConstants; +import com.termux.app.utils.Logger; import java.io.File; import java.util.Arrays; @@ -237,7 +236,7 @@ public final class TermuxPropertyConstants { if (propertiesFile.isFile() && propertiesFile.canRead()) { return propertiesFile; } else { - Log.d("termux", "No readable termux.properties file found"); + Logger.logDebug("No readable termux.properties file found"); return null; } } diff --git a/app/src/main/java/com/termux/app/settings/properties/TermuxSharedProperties.java b/app/src/main/java/com/termux/app/settings/properties/TermuxSharedProperties.java index 70a3dc3e..8c72793f 100644 --- a/app/src/main/java/com/termux/app/settings/properties/TermuxSharedProperties.java +++ b/app/src/main/java/com/termux/app/settings/properties/TermuxSharedProperties.java @@ -2,13 +2,12 @@ package com.termux.app.settings.properties; import android.content.Context; import android.content.res.Configuration; -import android.util.Log; -import android.widget.Toast; import androidx.annotation.Nullable; import com.termux.app.terminal.extrakeys.ExtraKeysInfo; import com.termux.app.terminal.KeyboardShortcut; +import com.termux.app.utils.Logger; import org.json.JSONException; @@ -30,6 +29,8 @@ public class TermuxSharedProperties implements SharedPropertiesParser { private ExtraKeysInfo mExtraKeysInfo; private final List mSessionShortcuts = new ArrayList<>(); + private static final String LOG_TAG = "TermuxSharedProperties"; + public TermuxSharedProperties(@Nonnull Context context) { mContext = context; mPropertiesFile = TermuxPropertyConstants.getTermuxPropertiesFile(); @@ -65,14 +66,14 @@ public class TermuxSharedProperties implements SharedPropertiesParser { String extraKeysStyle = (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_EXTRA_KEYS_STYLE, true); mExtraKeysInfo = new ExtraKeysInfo(extrakeys, extraKeysStyle); } catch (JSONException e) { - Toast.makeText(mContext, "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: " + e.toString(), Toast.LENGTH_LONG).show(); - Log.e("termux", "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: ", e); + Logger.showToast(mContext, "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: " + e.toString(), true); + Logger.logStackTraceWithMessage(LOG_TAG, "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: ", e); try { mExtraKeysInfo = new ExtraKeysInfo(TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS, TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS_STYLE); } catch (JSONException e2) { - Toast.makeText(mContext, "Can't create default extra keys", Toast.LENGTH_LONG).show(); - Log.e("termux", "Could create default extra keys: ", e); + Logger.showToast(mContext, "Can't create default extra keys",true); + Logger.logStackTraceWithMessage(LOG_TAG, "Could create default extra keys: ", e); mExtraKeysInfo = null; } } @@ -262,7 +263,7 @@ public class TermuxSharedProperties implements SharedPropertiesParser { // A null value can still be returned by // {@link #getInternalPropertyValueFromValue(Context,String,String)} for some keys value = getInternalPropertyValueFromValue(mContext, key, null); - Log.w("termux", "The value for \"" + key + "\" not found in SharedProperties cahce, force returning default value: `" + value + "`"); + Logger.logWarn(LOG_TAG, "The value for \"" + key + "\" not found in SharedProperties cahce, force returning default value: `" + value + "`"); return value; } } else { @@ -423,7 +424,7 @@ public class TermuxSharedProperties implements SharedPropertiesParser { String[] parts = value.toLowerCase().trim().split("\\+"); String input = parts.length == 2 ? parts[1].trim() : null; if (!(parts.length == 2 && parts[0].trim().equals("ctrl")) || input.isEmpty() || input.length() > 2) { - Log.e("termux", "Keyboard shortcut '" + key + "' is not Ctrl+"); + Logger.logError(LOG_TAG, "Keyboard shortcut '" + key + "' is not Ctrl+"); return null; } @@ -431,7 +432,7 @@ public class TermuxSharedProperties implements SharedPropertiesParser { int codePoint = c; if (Character.isLowSurrogate(c)) { if (input.length() != 2 || Character.isHighSurrogate(input.charAt(1))) { - Log.e("termux", "Keyboard shortcut '" + key + "' is not Ctrl+"); + Logger.logError(LOG_TAG, "Keyboard shortcut '" + key + "' is not Ctrl+"); return null; } else { codePoint = Character.toCodePoint(input.charAt(1), c); @@ -556,7 +557,7 @@ public class TermuxSharedProperties implements SharedPropertiesParser { propertiesDump.append(" null"); } - Log.d("termux", propertiesDump.toString()); + Logger.logDebug(LOG_TAG, propertiesDump.toString()); } public void dumpInternalPropertiesToLog() { @@ -570,7 +571,7 @@ public class TermuxSharedProperties implements SharedPropertiesParser { } } - Log.d("termux", internalPropertiesDump.toString()); + Logger.logDebug(LOG_TAG, internalPropertiesDump.toString()); } } diff --git a/app/src/main/java/com/termux/app/terminal/TermuxViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxViewClient.java index 567ebef8..8d49b6df 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxViewClient.java @@ -13,6 +13,7 @@ import com.termux.app.TermuxActivity; import com.termux.app.TermuxService; import com.termux.app.terminal.extrakeys.ExtraKeysView; import com.termux.app.settings.properties.TermuxPropertyConstants; +import com.termux.app.utils.Logger; import com.termux.terminal.KeyHandler; import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalSession; @@ -20,6 +21,7 @@ import com.termux.view.TerminalViewClient; import java.util.List; + import androidx.drawerlayout.widget.DrawerLayout; public final class TermuxViewClient implements TerminalViewClient { @@ -43,6 +45,8 @@ public final class TermuxViewClient implements TerminalViewClient { return scale; } + + @Override public void onSingleTapUp(MotionEvent e) { InputMethodManager mgr = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); @@ -64,12 +68,16 @@ public final class TermuxViewClient implements TerminalViewClient { return mActivity.getProperties().isUsingCtrlSpaceWorkaround(); } + + @Override public void copyModeChanged(boolean copyMode) { // Disable drawer while copying. mActivity.getDrawer().setDrawerLockMode(copyMode ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED); } + + @SuppressLint("RtlHardcoded") @Override public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession) { @@ -127,6 +135,26 @@ public final class TermuxViewClient implements TerminalViewClient { return handleVirtualKeys(keyCode, e, false); } + /** Handle dedicated volume buttons as virtual keys if applicable. */ + private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) { + InputDevice inputDevice = event.getDevice(); + if (mActivity.getProperties().areVirtualVolumeKeysDisabled()) { + return false; + } else if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + // Do not steal dedicated buttons from a full external keyboard. + return false; + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + mVirtualControlKeyDown = down; + return true; + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + mVirtualFnKeyDown = down; + return true; + } + return false; + } + + + @Override public boolean readControlKey() { return (mActivity.getExtraKeysView() != null && mActivity.getExtraKeysView().readSpecialButton(ExtraKeysView.SpecialButton.CTRL)) || mVirtualControlKeyDown; @@ -137,6 +165,13 @@ public final class TermuxViewClient implements TerminalViewClient { return (mActivity.getExtraKeysView() != null && mActivity.getExtraKeysView().readSpecialButton(ExtraKeysView.SpecialButton.ALT)); } + @Override + public boolean onLongPress(MotionEvent event) { + return false; + } + + + @Override public boolean onCodePoint(final int codePoint, boolean ctrlDown, TerminalSession session) { if (mVirtualFnKeyDown) { @@ -273,27 +308,41 @@ public final class TermuxViewClient implements TerminalViewClient { return false; } + + @Override - public boolean onLongPress(MotionEvent event) { - return false; + public void logError(String tag, String message) { + Logger.logError(tag, message); } - /** Handle dedicated volume buttons as virtual keys if applicable. */ - private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) { - InputDevice inputDevice = event.getDevice(); - if (mActivity.getProperties().areVirtualVolumeKeysDisabled()) { - return false; - } else if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { - // Do not steal dedicated buttons from a full external keyboard. - return false; - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - mVirtualControlKeyDown = down; - return true; - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { - mVirtualFnKeyDown = down; - return true; - } - return false; + @Override + public void logWarn(String tag, String message) { + Logger.logWarn(tag, message); + } + + @Override + public void logInfo(String tag, String message) { + Logger.logInfo(tag, message); + } + + @Override + public void logDebug(String tag, String message) { + Logger.logDebug(tag, message); + } + + @Override + public void logVerbose(String tag, String message) { + Logger.logVerbose(tag, message); + } + + @Override + public void logStackTraceWithMessage(String tag, String message, Exception e) { + Logger.logStackTraceWithMessage(tag, message, e); + } + + @Override + public void logStackTrace(String tag, Exception e) { + Logger.logStackTrace(tag, e); } } diff --git a/app/src/main/java/com/termux/app/utils/Logger.java b/app/src/main/java/com/termux/app/utils/Logger.java new file mode 100644 index 00000000..aace3c25 --- /dev/null +++ b/app/src/main/java/com/termux/app/utils/Logger.java @@ -0,0 +1,223 @@ +package com.termux.app.utils; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; + +import com.termux.R; +import com.termux.app.TermuxConstants; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +public class Logger { + + public static final String DEFAULT_LOG_TAG = TermuxConstants.TERMUX_APP_NAME; + + public static final int LOG_LEVEL_OFF = 0; // log nothing + public static final int LOG_LEVEL_NORMAL = 1; // start logging error, warn and info messages and stacktraces + public static final int LOG_LEVEL_DEBUG = 2; // start logging debug messages + public static final int LOG_LEVEL_VERBOSE = 3; // start logging verbose messages + + public static final int DEFAULT_LOG_LEVEL = LOG_LEVEL_NORMAL; + + private static int CURRENT_LOG_LEVEL = DEFAULT_LOG_LEVEL; + + static public void logMesssage(int logLevel, String tag, String message) { + if(logLevel == Log.ERROR && CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL) + Log.e(tag, message); + else if(logLevel == Log.WARN && CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL) + Log.w(tag, message); + else if(logLevel == Log.INFO && CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL) + Log.i(tag, message); + else if(logLevel == Log.DEBUG && CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG) + Log.d(tag, message); + else if(logLevel == Log.VERBOSE && CURRENT_LOG_LEVEL >= LOG_LEVEL_VERBOSE) + Log.v(tag, message); + } + + + + static public void logError(String tag, String message) { + logMesssage(Log.ERROR, tag, message); + } + + static public void logError(String message) { + logMesssage(Log.ERROR, DEFAULT_LOG_TAG, message); + } + + + + static public void logWarn(String tag, String message) { + logMesssage(Log.WARN, tag, message); + } + + static public void logWarn(String message) { + logMesssage(Log.WARN, DEFAULT_LOG_TAG, message); + } + + + + static public void logInfo(String tag, String message) { + logMesssage(Log.INFO, tag, message); + } + + static public void logInfo(String message) { + logMesssage(Log.INFO, DEFAULT_LOG_TAG, message); + } + + + + static public void logDebug(String tag, String message) { + logMesssage(Log.DEBUG, tag, message); + } + + static public void logDebug(String message) { + logMesssage(Log.DEBUG, DEFAULT_LOG_TAG, message); + } + + + + static public void logVerbose(String tag, String message) { + logMesssage(Log.VERBOSE, tag, message); + } + + static public void logVerbose(String message) { + logMesssage(Log.VERBOSE, DEFAULT_LOG_TAG, message); + } + + + + static public void logErrorAndShowToast(Context context, String tag, String message) { + if (context == null) return; + + if(CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL) { + logError(tag, message); + showToast(context, message, true); + } + } + + static public void logErrorAndShowToast(Context context, String message) { + logErrorAndShowToast(context, DEFAULT_LOG_TAG, message); + } + + + + static public void logDebugAndShowToast(Context context, String tag, String message) { + if (context == null) return; + + if(CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG) { + logDebug(tag, message); + showToast(context, message, true); + } + } + + static public void logDebugAndShowToast(Context context, String message) { + logDebugAndShowToast(context, DEFAULT_LOG_TAG, message); + } + + + + static public void logStackTraceWithMessage(String tag, String message, Exception e) { + + if(CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL) + { + try { + StringWriter errors = new StringWriter(); + PrintWriter pw = new PrintWriter(errors); + e.printStackTrace(pw); + pw.close(); + if(message != null) + Log.e(tag, message + ":\n" + errors.toString()); + else + Log.e(tag, errors.toString()); + errors.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + + static public void logStackTraceWithMessage(String message, Exception e) { + logStackTraceWithMessage(DEFAULT_LOG_TAG, message, e); + } + + static public void logStackTrace(String tag, Exception e) { + logStackTraceWithMessage(tag, null, e); + } + + static public void logStackTrace(Exception e) { + logStackTraceWithMessage(DEFAULT_LOG_TAG, null, e); + } + + + + static public void showToast(final Context context, final String toastText, boolean longDuration) { + if (context == null) return; + + new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(context, toastText, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show()); + } + + + + public static CharSequence[] getLogLevelsArray() { + return new CharSequence[]{ + String.valueOf(LOG_LEVEL_OFF), + String.valueOf(LOG_LEVEL_NORMAL), + String.valueOf(LOG_LEVEL_DEBUG), + String.valueOf(LOG_LEVEL_VERBOSE) + }; + } + + public static CharSequence[] getLogLevelLabelsArray(Context context, CharSequence[] logLevels, boolean addDefaultTag) { + if (logLevels == null) return null; + + CharSequence[] logLevelLabels = new CharSequence[logLevels.length]; + + for(int i=0; i= LOG_LEVEL_OFF && logLevel <= LOG_LEVEL_VERBOSE) + CURRENT_LOG_LEVEL = logLevel; + else + CURRENT_LOG_LEVEL = DEFAULT_LOG_LEVEL; + + if(context != null) + showToast(context, context.getString(R.string.log_level_value, getLogLevelLabel(context, CURRENT_LOG_LEVEL, false)),true); + + return CURRENT_LOG_LEVEL; + } + +} diff --git a/app/src/main/java/com/termux/app/utils/TermuxUtils.java b/app/src/main/java/com/termux/app/utils/TermuxUtils.java new file mode 100644 index 00000000..712b5b3a --- /dev/null +++ b/app/src/main/java/com/termux/app/utils/TermuxUtils.java @@ -0,0 +1,18 @@ +package com.termux.app.utils; + +import android.content.Context; + +import com.termux.app.TermuxConstants; + +public class TermuxUtils { + + public static Context getTermuxPackageContext(Context context) { + try { + return context.createPackageContext(TermuxConstants.TERMUX_PACKAGE_NAME, Context.CONTEXT_RESTRICTED); + } catch (Exception e) { + Logger.logStackTraceWithMessage("Failed to get \"" + TermuxConstants.TERMUX_PACKAGE_NAME + "\" package context. Force using current context.", e); + Logger.logError("Force using current context"); + return context; + } + } +} diff --git a/app/src/main/java/com/termux/filepicker/TermuxFileReceiverActivity.java b/app/src/main/java/com/termux/filepicker/TermuxFileReceiverActivity.java index 2367bdf0..f0f53dcb 100644 --- a/app/src/main/java/com/termux/filepicker/TermuxFileReceiverActivity.java +++ b/app/src/main/java/com/termux/filepicker/TermuxFileReceiverActivity.java @@ -6,7 +6,6 @@ import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.OpenableColumns; -import android.util.Log; import android.util.Patterns; import com.termux.R; @@ -14,6 +13,7 @@ import com.termux.app.DialogUtils; import com.termux.app.TermuxConstants; import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_SERVICE; import com.termux.app.TermuxService; +import com.termux.app.utils.Logger; import java.io.ByteArrayInputStream; import java.io.File; @@ -39,6 +39,8 @@ public class TermuxFileReceiverActivity extends Activity { */ boolean mFinishOnDismissNameDialog = true; + private static final String LOG_TAG = "TermuxFileReceiverActivity"; + static boolean isSharedTextAnUrl(String sharedText) { return Patterns.WEB_URL.matcher(sharedText).matches() || Pattern.matches("magnet:\\?xt=urn:btih:.*?", sharedText); @@ -111,7 +113,7 @@ public class TermuxFileReceiverActivity extends Activity { promptNameAndSave(in, attachmentFileName); } catch (Exception e) { showErrorDialogAndQuit("Unable to handle shared content:\n\n" + e.getMessage()); - Log.e("termux", "handleContentUri(uri=" + uri + ") failed", e); + Logger.logStackTraceWithMessage(LOG_TAG, "handleContentUri(uri=" + uri + ") failed", e); } } @@ -171,7 +173,7 @@ public class TermuxFileReceiverActivity extends Activity { return outFile; } catch (IOException e) { showErrorDialogAndQuit("Error saving file:\n\n" + e); - Log.e("termux", "Error saving file", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error saving file", e); return null; } } diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml new file mode 100644 index 00000000..7dc1a6c5 --- /dev/null +++ b/app/src/main/res/layout/settings_activity.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a34c5d11..968ac645 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,4 +63,35 @@ Save file in ~/downloads/ Edit Open folder + + + + + + + Settings + Termux Settings + + + + + Debugging + + + Logging + + + Log Level + "Off" + "Normal" + "Debug" + "Verbose" + "*Unknown*" + Logcat log level set to \"%1$s\" + + + Terminal View Key Logging + Logs will not have entries for terminal view keys. (Default) + Logcat logs will have entries for terminal view keys. These are very verbose and should be disabled under normal circumstances or will cause performance issues. + diff --git a/app/src/main/res/xml/debugging_preferences.xml b/app/src/main/res/xml/debugging_preferences.xml new file mode 100644 index 00000000..ba1f328d --- /dev/null +++ b/app/src/main/res/xml/debugging_preferences.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml new file mode 100644 index 00000000..3ea2b583 --- /dev/null +++ b/app/src/main/res/xml/root_preferences.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/terminal-view/src/main/java/com/termux/view/TerminalView.java b/terminal-view/src/main/java/com/termux/view/TerminalView.java index b9d18e69..995f8b9a 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalView.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalView.java @@ -12,7 +12,6 @@ import android.text.Editable; import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.view.ActionMode; import android.view.HapticFeedbackConstants; import android.view.InputDevice; @@ -31,7 +30,6 @@ import android.widget.Scroller; import androidx.annotation.RequiresApi; -import com.termux.terminal.EmulatorDebug; import com.termux.terminal.KeyHandler; import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalSession; @@ -40,8 +38,8 @@ import com.termux.view.textselection.TextSelectionCursorController; /** View displaying and interacting with a {@link TerminalSession}. */ public final class TerminalView extends View { - /** Log view key and IME events. */ - private static final boolean LOG_KEY_EVENTS = false; + /** Log terminal view key and IME events. */ + private static boolean TERMINAL_VIEW_KEY_LOGGING_ENABLED = false; /** The currently displayed terminal session, whose emulator is {@link #mEmulator}. */ public TerminalSession mTermSession; @@ -76,6 +74,8 @@ public final class TerminalView extends View { private final boolean mAccessibilityEnabled; + private static final String LOG_TAG = "TerminalView"; + public TerminalView(Context context, AttributeSet attributes) { // NO_UCD (unused code) super(context, attributes); mGestureRecognizer = new GestureAndScaleRecognizer(context, new GestureAndScaleRecognizer.Listener() { @@ -210,11 +210,23 @@ public final class TerminalView extends View { } /** - * @param onKeyListener Listener for all kinds of key events, both hardware and IME (which makes it different from that - * available with {@link View#setOnKeyListener(OnKeyListener)}. + * @param terminalViewClient Interface for communicating with the terminal view client. It allows + * for getting various configuration options from the client and + * for sending back data to the client like logs, key events, both + * hardware and IME (which makes it different from that available with + * {@link View#setOnKeyListener(OnKeyListener)}, etc. */ - public void setOnKeyListener(TerminalViewClient onKeyListener) { - this.mClient = onKeyListener; + public void setTerminalViewClient(TerminalViewClient terminalViewClient) { + this.mClient = terminalViewClient; + } + + /** + * Sets terminal view key logging is enabled or not. + * + * @param value The boolean value that defines the state. + */ + public void setIsTerminalViewKeyLoggingEnabled(boolean value) { + TERMINAL_VIEW_KEY_LOGGING_ENABLED = value; } /** @@ -264,7 +276,7 @@ public final class TerminalView extends View { @Override public boolean finishComposingText() { - if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: finishComposingText()"); + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) mClient.logInfo(LOG_TAG, "IME: finishComposingText()"); super.finishComposingText(); sendTextToTerminal(getEditable()); @@ -274,8 +286,8 @@ public final class TerminalView extends View { @Override public boolean commitText(CharSequence text, int newCursorPosition) { - if (LOG_KEY_EVENTS) { - Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")"); + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) { + mClient.logInfo(LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")"); } super.commitText(text, newCursorPosition); @@ -289,8 +301,8 @@ public final class TerminalView extends View { @Override public boolean deleteSurroundingText(int leftLength, int rightLength) { - if (LOG_KEY_EVENTS) { - Log.i(EmulatorDebug.LOG_TAG, "IME: deleteSurroundingText(" + leftLength + ", " + rightLength + ")"); + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) { + mClient.logInfo(LOG_TAG, "IME: deleteSurroundingText(" + leftLength + ", " + rightLength + ")"); } // The stock Samsung keyboard with 'Auto check spelling' enabled sends leftLength > 1. KeyEvent deleteKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL); @@ -521,8 +533,8 @@ public final class TerminalView extends View { @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) - Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")"); + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) + mClient.logInfo(LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")"); if (keyCode == KeyEvent.KEYCODE_BACK) { if (isSelectingText()) { stopTextSelectionMode(); @@ -547,8 +559,8 @@ public final class TerminalView extends View { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) - Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")"); + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) + mClient.logInfo(LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")"); if (mEmulator == null) return true; if (isSelectingText()) { stopTextSelectionMode(); @@ -575,7 +587,7 @@ public final class TerminalView extends View { if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT; if (event.isNumLockOn()) keyMod |= KeyHandler.KEYMOD_NUM_LOCK; if (!event.isFunctionPressed() && handleKeyCode(keyCode, keyMod)) { - if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleKeyCode() took key event"); + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) mClient.logInfo(LOG_TAG, "handleKeyCode() took key event"); return true; } @@ -590,8 +602,8 @@ public final class TerminalView extends View { int effectiveMetaState = event.getMetaState() & ~bitsToClear; int result = event.getUnicodeChar(effectiveMetaState); - if (LOG_KEY_EVENTS) - Log.i(EmulatorDebug.LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result); + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) + mClient.logInfo(LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result); if (result == 0) { return false; } @@ -617,8 +629,8 @@ public final class TerminalView extends View { } public void inputCodePoint(int codePoint, boolean controlDownFromEvent, boolean leftAltDownFromEvent) { - if (LOG_KEY_EVENTS) { - Log.i(EmulatorDebug.LOG_TAG, "inputCodePoint(codePoint=" + codePoint + ", controlDownFromEvent=" + controlDownFromEvent + ", leftAltDownFromEvent=" + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) { + mClient.logInfo(LOG_TAG, "inputCodePoint(codePoint=" + codePoint + ", controlDownFromEvent=" + controlDownFromEvent + ", leftAltDownFromEvent=" + leftAltDownFromEvent + ")"); } @@ -692,8 +704,8 @@ public final class TerminalView extends View { */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) - Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")"); + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) + mClient.logInfo(LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")"); if (mEmulator == null) return true; if (mClient.onKeyUp(keyCode, event)) { diff --git a/terminal-view/src/main/java/com/termux/view/TerminalViewClient.java b/terminal-view/src/main/java/com/termux/view/TerminalViewClient.java index 390e9157..25be7d9d 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalViewClient.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalViewClient.java @@ -8,7 +8,7 @@ import com.termux.terminal.TerminalSession; /** * Input and scale listener which may be set on a {@link TerminalView} through - * {@link TerminalView#setOnKeyListener(TerminalViewClient)}. + * {@link TerminalView#setTerminalViewClient(TerminalViewClient)}. *

*/ public interface TerminalViewClient { @@ -18,6 +18,8 @@ public interface TerminalViewClient { */ float onScale(float scale); + + /** * On a single tap on the terminal if terminal mouse reporting not enabled. */ @@ -29,18 +31,41 @@ public interface TerminalViewClient { boolean shouldUseCtrlSpaceWorkaround(); + + void copyModeChanged(boolean copyMode); + + boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session); boolean onKeyUp(int keyCode, KeyEvent e); + boolean onLongPress(MotionEvent event); + + + boolean readControlKey(); boolean readAltKey(); + boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session); - boolean onLongPress(MotionEvent event); + + + void logError(String tag, String message); + + void logWarn(String tag, String message); + + void logInfo(String tag, String message); + + void logDebug(String tag, String message); + + void logVerbose(String tag, String message); + + void logStackTraceWithMessage(String tag, String message, Exception e); + + void logStackTrace(String tag, Exception e); }