Skip to content

Commit d39972b

Browse files
Implement GUI based Termux settings manager and a centralized logging framework
The settings activity can be accessed by long pressing on terminal view and selecting "Settings" from the popup shown. It uses the Android's Preference framework. Currently only debugging preferences to set log level and enabling terminal view key logging are provided. The Preference framework by default uses the keys set in `app:key` attribute in the respective preferences XML file to store the values in the default `SharedPreferences` file of the app. However, since we rely on `TermuxPreferenceConstants` and `TermuxPropertyConstants` classes to define key names so that they can be easily shared between termux and its plugin apps, we provide our own `PreferenceDataStore` for storing key/value pairs. The key name in the XML file can optionally be the same. Check `DebuggingPreferencesFragment` class for a sample. Each new preference category fragment should be added to `app/settings/` with its data store. This commit may allow support to be added for modifying `termux.properties` file directly from the UI but that requires more work, since writing to property files with comments require in-place modification. The `Logger` class provides various static functions for logging that should be used from now on instead of directly calling android `Log.*` functions. The log level is automatically loaded from shared preferences at application startup via `TermuxApplication` and set in the static `Logger.CURRENT_LOG_LEVEL` variable. Changing the log level through the settings activity also changes the log level immediately. The 4 supported log levels are: - LOG_LEVEL_OFF which will log nothing. - LOG_LEVEL_NORMAL which will start logging error, warn and info messages and stacktraces. - LOG_LEVEL_DEBUG which will start logging debug messages. - LOG_LEVEL_VERBOSE which will start logging verbose messages. The default log level is `LOG_LEVEL_NORMAL` which will not log debug or verbose messages. Contributors can add useful log entries at those levels where ever they feel is appropriate so that it allows users and devs to more easily help solve issues or find bugs, specially without having to recompile termux after having to manually add general log entries to the source. DO NOT log data that may have private info of users like command arguments at log levels below debug, like `BackgroundJob` was doing previously. Logging to file support may be added later, will require log file rotation support and storage permissions.
1 parent 93b506a commit d39972b

26 files changed

+854
-178
lines changed

app/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ android {
8787
}
8888

8989
dependencies {
90+
implementation 'androidx.appcompat:appcompat:1.2.0'
91+
implementation 'androidx.preference:preference:1.1.1'
9092
testImplementation 'junit:junit:4.13.1'
9193
testImplementation 'org.robolectric:robolectric:4.4'
9294
}

app/src/main/AndroidManifest.xml

+81-50
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?>
12
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
23
xmlns:tools="http://schemas.android.com/tools"
34
package="com.termux"
45
android:installLocation="internalOnly"
56
android:sharedUserId="${TERMUX_PACKAGE_NAME}"
6-
android:sharedUserLabel="@string/shared_user_label" >
7+
android:sharedUserLabel="@string/shared_user_label">
78

8-
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
9-
<uses-feature android:name="android.software.leanback" android:required="false" />
9+
<uses-feature
10+
android:name="android.hardware.touchscreen"
11+
android:required="false" />
12+
<uses-feature
13+
android:name="android.software.leanback"
14+
android:required="false" />
1015

11-
<permission android:name="${TERMUX_PACKAGE_NAME}.permission.RUN_COMMAND"
12-
android:label="@string/run_command_permission_label"
16+
<permission
17+
android:name="${TERMUX_PACKAGE_NAME}.permission.RUN_COMMAND"
1318
android:description="@string/run_command_permission_description"
1419
android:icon="@mipmap/ic_launcher"
20+
android:label="@string/run_command_permission_label"
1521
android:protectionLevel="dangerous" />
1622

1723
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -20,65 +26,95 @@
2026
<uses-permission android:name="android.permission.WAKE_LOCK" />
2127
<uses-permission android:name="android.permission.VIBRATE" />
2228
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
23-
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
24-
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
29+
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
30+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
2531
<uses-permission android:name="android.permission.READ_LOGS" />
2632
<uses-permission android:name="android.permission.DUMP" />
2733
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
2834
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
2935

3036
<application
31-
android:label="@string/application_name"
32-
android:icon="@mipmap/ic_launcher"
33-
android:roundIcon="@mipmap/ic_launcher_round"
37+
android:name=".app.TermuxApplication"
38+
android:allowBackup="false"
3439
android:banner="@drawable/banner"
35-
android:theme="@style/Theme.Termux"
36-
3740
android:extractNativeLibs="true"
38-
android:allowBackup="false"
39-
android:supportsRtl="false" >
41+
android:icon="@mipmap/ic_launcher"
42+
android:label="@string/application_name"
43+
android:roundIcon="@mipmap/ic_launcher_round"
44+
android:supportsRtl="false"
45+
android:theme="@style/Theme.Termux">
4046

41-
<!-- This (or rather, value 2.1 or higher) is needed to make the Samsung Galaxy S8
42-
mark the app with "This app is optimized to run in full screen." -->
43-
<meta-data android:name="android.max_aspect" android:value="10.0" />
47+
<!--
48+
This (or rather, value 2.1 or higher) is needed to make the Samsung Galaxy S8
49+
mark the app with "This app is optimized to run in full screen."
50+
-->
51+
<meta-data
52+
android:name="android.max_aspect"
53+
android:value="10.0" />
4454

4555
<activity
4656
android:name=".app.TermuxActivity"
47-
android:label="@string/application_name"
48-
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|uiMode|keyboard|keyboardHidden|navigation"
57+
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|uiMode|keyboard|keyboardHidden|navigation"
58+
android:label="@string/application_name"
4959
android:launchMode="singleTask"
5060
android:resizeableActivity="true"
51-
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" >
61+
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
5262
<intent-filter>
5363
<action android:name="android.intent.action.MAIN" />
64+
5465
<category android:name="android.intent.category.LAUNCHER" />
5566
</intent-filter>
5667
<intent-filter>
5768
<action android:name="android.intent.action.MAIN" />
69+
5870
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
5971
</intent-filter>
60-
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
72+
73+
<meta-data
74+
android:name="android.app.shortcuts"
75+
android:resource="@xml/shortcuts" />
6176
</activity>
6277

78+
<activity-alias
79+
android:name=".HomeActivity"
80+
android:targetActivity=".app.TermuxActivity">
81+
82+
<!-- Launch activity automatically on boot on Android Things devices -->
83+
<intent-filter>
84+
<action android:name="android.intent.action.MAIN" />
85+
86+
<category android:name="android.intent.category.IOT_LAUNCHER" />
87+
<category android:name="android.intent.category.DEFAULT" />
88+
</intent-filter>
89+
</activity-alias>
90+
6391
<activity
6492
android:name=".app.TermuxHelpActivity"
6593
android:exported="false"
66-
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
94+
android:label="@string/application_name"
6795
android:parentActivityName=".app.TermuxActivity"
6896
android:resizeableActivity="true"
69-
android:label="@string/application_name" />
97+
android:theme="@android:style/Theme.Material.Light.DarkActionBar" />
98+
99+
<activity
100+
android:name=".app.TermuxSettingsActivity"
101+
android:label="@string/title_activity_termux_settings"
102+
android:theme="@style/Theme.AppCompat.Light.DarkActionBar" />
70103

71104
<activity
72105
android:name=".filepicker.TermuxFileReceiverActivity"
73-
android:label="@string/application_name"
74-
android:taskAffinity="${TERMUX_PACKAGE_NAME}.filereceiver"
75106
android:excludeFromRecents="true"
107+
android:label="@string/application_name"
108+
android:noHistory="true"
76109
android:resizeableActivity="true"
77-
android:noHistory="true">
110+
android:taskAffinity="${TERMUX_PACKAGE_NAME}.filereceiver">
111+
78112
<!-- Accept multiple file types when sending. -->
79113
<intent-filter>
80-
<action android:name="android.intent.action.SEND"/>
114+
<action android:name="android.intent.action.SEND" />
115+
81116
<category android:name="android.intent.category.DEFAULT" />
117+
82118
<data android:mimeType="application/*" />
83119
<data android:mimeType="audio/*" />
84120
<data android:mimeType="image/*" />
@@ -89,8 +125,10 @@
89125
</intent-filter>
90126
<!-- Accept multiple file types to let Termux be usable as generic file viewer. -->
91127
<intent-filter tools:ignore="AppLinkUrlError">
92-
<action android:name="android.intent.action.VIEW"/>
128+
<action android:name="android.intent.action.VIEW" />
129+
93130
<category android:name="android.intent.category.DEFAULT" />
131+
94132
<data android:mimeType="application/*" />
95133
<data android:mimeType="audio/*" />
96134
<data android:mimeType="image/*" />
@@ -99,23 +137,11 @@
99137
</intent-filter>
100138
</activity>
101139

102-
<activity-alias
103-
android:name=".HomeActivity"
104-
android:targetActivity=".app.TermuxActivity">
105-
106-
<!-- Launch activity automatically on boot on Android Things devices -->
107-
<intent-filter>
108-
<action android:name="android.intent.action.MAIN"/>
109-
<category android:name="android.intent.category.IOT_LAUNCHER"/>
110-
<category android:name="android.intent.category.DEFAULT"/>
111-
</intent-filter>
112-
</activity-alias>
113-
114140
<provider
115141
android:name=".filepicker.TermuxDocumentsProvider"
116142
android:authorities="${TERMUX_PACKAGE_NAME}.documents"
117-
android:grantUriPermissions="true"
118143
android:exported="true"
144+
android:grantUriPermissions="true"
119145
android:permission="android.permission.MANAGE_DOCUMENTS">
120146
<intent-filter>
121147
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
@@ -125,25 +151,30 @@
125151
<service
126152
android:name=".app.TermuxService"
127153
android:exported="false" />
128-
129154
<service
130155
android:name=".app.RunCommandService"
131156
android:exported="true"
132-
android:permission="${TERMUX_PACKAGE_NAME}.permission.RUN_COMMAND" >
157+
android:permission="${TERMUX_PACKAGE_NAME}.permission.RUN_COMMAND">
133158
<intent-filter>
134159
<action android:name="${TERMUX_PACKAGE_NAME}.RUN_COMMAND" />
135160
</intent-filter>
136161
</service>
137162

138163
<receiver android:name=".app.TermuxOpenReceiver" />
139164

140-
<provider android:authorities="${TERMUX_PACKAGE_NAME}.files"
141-
android:readPermission="android.permission.permRead"
142-
android:exported="true"
143-
android:grantUriPermissions="true"
144-
android:name=".app.TermuxOpenReceiver$ContentProvider" />
145-
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
146-
<meta-data android:name="com.samsung.android.multidisplay.keep_process_alive" android:value="true"/>
165+
<provider
166+
android:name=".app.TermuxOpenReceiver$ContentProvider"
167+
android:authorities="${TERMUX_PACKAGE_NAME}.files"
168+
android:exported="true"
169+
android:grantUriPermissions="true"
170+
android:readPermission="android.permission.permRead" />
171+
172+
<meta-data
173+
android:name="com.sec.android.support.multiwindow"
174+
android:value="true" />
175+
<meta-data
176+
android:name="com.samsung.android.multidisplay.keep_process_alive"
177+
android:value="true" />
147178
</application>
148179

149180
</manifest>

app/src/main/java/com/termux/app/BackgroundJob.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import android.app.PendingIntent;
55
import android.content.Intent;
66
import android.os.Bundle;
7-
import android.util.Log;
87

98
import com.termux.BuildConfig;
9+
import com.termux.app.utils.Logger;
1010

1111
import java.io.BufferedReader;
1212
import java.io.File;
@@ -26,10 +26,10 @@
2626
*/
2727
public final class BackgroundJob {
2828

29-
private static final String LOG_TAG = "termux-task";
30-
3129
final Process mProcess;
3230

31+
private static final String LOG_TAG = "BackgroundJob";
32+
3333
public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service){
3434
this(cwd, fileToExecute, args, service, null);
3535
}
@@ -47,7 +47,7 @@ public BackgroundJob(String cwd, String fileToExecute, final String[] args, fina
4747
} catch (IOException e) {
4848
mProcess = null;
4949
// TODO: Visible error message?
50-
Log.e(LOG_TAG, "Failed running background job: " + processDescription, e);
50+
Logger.logStackTraceWithMessage(LOG_TAG, "Failed running background job: " + processDescription, e);
5151
return;
5252
}
5353

@@ -67,7 +67,7 @@ public void run() {
6767
// FIXME: Long lines.
6868
while ((line = reader.readLine()) != null) {
6969
errResult.append(line).append('\n');
70-
Log.i(LOG_TAG, "[" + pid + "] stderr: " + line);
70+
Logger.logDebug(LOG_TAG, "[" + pid + "] stderr: " + line);
7171
}
7272
} catch (IOException e) {
7373
// Ignore.
@@ -79,28 +79,28 @@ public void run() {
7979
new Thread() {
8080
@Override
8181
public void run() {
82-
Log.i(LOG_TAG, "[" + pid + "] starting: " + processDescription);
82+
Logger.logDebug(LOG_TAG, "[" + pid + "] starting: " + processDescription);
8383
InputStream stdout = mProcess.getInputStream();
8484
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
8585

8686
String line;
8787
try {
8888
// FIXME: Long lines.
8989
while ((line = reader.readLine()) != null) {
90-
Log.i(LOG_TAG, "[" + pid + "] stdout: " + line);
90+
Logger.logDebug(LOG_TAG, "[" + pid + "] stdout: " + line);
9191
outResult.append(line).append('\n');
9292
}
9393
} catch (IOException e) {
94-
Log.e(LOG_TAG, "Error reading output", e);
94+
Logger.logStackTraceWithMessage(LOG_TAG, "Error reading output", e);
9595
}
9696

9797
try {
9898
int exitCode = mProcess.waitFor();
9999
service.onBackgroundJobExited(BackgroundJob.this);
100100
if (exitCode == 0) {
101-
Log.i(LOG_TAG, "[" + pid + "] exited normally");
101+
Logger.logDebug(LOG_TAG, "[" + pid + "] exited normally");
102102
} else {
103-
Log.w(LOG_TAG, "[" + pid + "] exited with code: " + exitCode);
103+
Logger.logDebug(LOG_TAG, "[" + pid + "] exited with code: " + exitCode);
104104
}
105105

106106
result.putString("stdout", outResult.toString());

app/src/main/java/com/termux/app/RunCommandService.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
import android.os.Binder;
1111
import android.os.Build;
1212
import android.os.IBinder;
13-
import android.util.Log;
1413

1514
import com.termux.R;
1615
import com.termux.app.TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE;
1716
import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;
1817
import com.termux.app.settings.properties.TermuxPropertyConstants;
1918
import com.termux.app.settings.properties.TermuxSharedProperties;
19+
import com.termux.app.utils.Logger;
2020

2121
import java.io.File;
2222
import java.io.FileInputStream;
@@ -84,6 +84,8 @@ public class RunCommandService extends Service {
8484
private static final String NOTIFICATION_CHANNEL_ID = "termux_run_command_notification_channel";
8585
private static final int NOTIFICATION_ID = 1338;
8686

87+
private static final String LOG_TAG = "RunCommandService";
88+
8789
class LocalBinder extends Binder {
8890
public final RunCommandService service = RunCommandService.this;
8991
}
@@ -107,13 +109,13 @@ public int onStartCommand(Intent intent, int flags, int startId) {
107109

108110
// If wrong action passed, then just return
109111
if (!RUN_COMMAND_SERVICE.ACTION_RUN_COMMAND.equals(intent.getAction())) {
110-
Log.e("termux", "Unexpected intent action to RunCommandService: " + intent.getAction());
112+
Logger.logError(LOG_TAG, "Unexpected intent action to RunCommandService: " + intent.getAction());
111113
return Service.START_NOT_STICKY;
112114
}
113115

114116
// If allow-external-apps property is not set to "true"
115117
if (!TermuxSharedProperties.isPropertyValueTrue(this, TermuxPropertyConstants.getTermuxPropertiesFile(), TermuxConstants.PROP_ALLOW_EXTERNAL_APPS)) {
116-
Log.e("termux", "RunCommandService requires allow-external-apps property to be set to \"true\" in \"" + TermuxConstants.TERMUX_PROPERTIES_PRIMARY_FILE_PATH + "\" file");
118+
Logger.logError(LOG_TAG, "RunCommandService requires allow-external-apps property to be set to \"true\" in \"" + TermuxConstants.TERMUX_PROPERTIES_PRIMARY_FILE_PATH + "\" file");
117119
return Service.START_NOT_STICKY;
118120
}
119121

@@ -155,7 +157,7 @@ private void runStopForeground() {
155157

156158
private Notification buildNotification() {
157159
Notification.Builder builder = new Notification.Builder(this);
158-
builder.setContentTitle(getText(R.string.application_name) + " Run Command");
160+
builder.setContentTitle(TermuxConstants.TERMUX_APP_NAME + " Run Command");
159161
builder.setSmallIcon(R.drawable.ic_service_notification);
160162

161163
// Use a low priority:
@@ -177,7 +179,7 @@ private Notification buildNotification() {
177179
private void setupNotificationChannel() {
178180
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
179181

180-
String channelName = "Termux Run Command";
182+
String channelName = TermuxConstants.TERMUX_APP_NAME + " Run Command";
181183
int importance = NotificationManager.IMPORTANCE_LOW;
182184

183185
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, importance);

0 commit comments

Comments
 (0)