From 44ee2ee775dbc2f96c2c1aab241558031a92d4f2 Mon Sep 17 00:00:00 2001 From: tareksander <57038324+tareksander@users.noreply.github.com> Date: Wed, 15 Dec 2021 22:10:49 +0100 Subject: [PATCH 001/142] Added: An API to access the Android SAF. --- app/src/main/AndroidManifest.xml | 4 + app/src/main/java/com/termux/api/SAFAPI.java | 290 ++++++++++++++++++ .../com/termux/api/TermuxApiReceiver.java | 3 + .../com/termux/api/util/ResultReturner.java | 26 ++ 4 files changed, 323 insertions(+) create mode 100644 app/src/main/java/com/termux/api/SAFAPI.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ff328bb67..d62ad02a9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -82,6 +82,10 @@ android:theme="@android:style/Theme.Translucent.NoTitleBar" android:excludeFromRecents="true" android:exported="false"/> + diff --git a/app/src/main/java/com/termux/api/SAFAPI.java b/app/src/main/java/com/termux/api/SAFAPI.java new file mode 100644 index 000000000..9d8351e51 --- /dev/null +++ b/app/src/main/java/com/termux/api/SAFAPI.java @@ -0,0 +1,290 @@ +package com.termux.api; + +import android.content.Context; +import android.content.Intent; +import android.content.UriPermission; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.FileUtils; +import android.util.JsonWriter; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.documentfile.provider.DocumentFile; + +import com.termux.api.util.ResultReturner; +import com.termux.api.util.TermuxApiLogger; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; + +public class SAFAPI +{ + public static class SAFActivity extends AppCompatActivity { + private boolean resultReturned = false; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + i.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + startActivityForResult(i, 0); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (! resultReturned) { + ResultReturner.returnData(this, getIntent(), out -> out.write("")); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (data != null) { + Uri uri = data.getData(); + if (uri != null) { + getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + resultReturned = true; + ResultReturner.returnData(this, getIntent(), out -> out.write(data.getDataString())); + } + } + finish(); + } + } + + static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + String method = intent.getStringExtra("safmethod"); + if (method == null) { + TermuxApiLogger.error("safmethod extra null"); + return; + } + try { + switch (method) { + case "getManagedDocumentTrees": + getManagedDocumentTrees(apiReceiver, context, intent); + break; + case "manageDocumentTree": + manageDocumentTree(context, intent); + break; + case "writeDocument": + writeDocument(apiReceiver, context, intent); + break; + case "createDocument": + createDocument(apiReceiver, context, intent); + break; + case "readDocument": + readDocument(apiReceiver, context, intent); + break; + case "listDirectory": + listDirectory(apiReceiver, context, intent); + break; + case "removeDocument": + removeDocument(apiReceiver, context, intent); + break; + case "statURI": + statURI(apiReceiver, context, intent); + break; + default: + TermuxApiLogger.error("Unrecognized safmethod: " + "'" + method + "'"); + } + } catch (Exception e) { + TermuxApiLogger.error("Error in SAFAPI", e); + } + } + + private static void getManagedDocumentTrees(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() + { + @Override + public void writeJson(JsonWriter out) throws Exception { + out.beginArray(); + for (UriPermission p : context.getContentResolver().getPersistedUriPermissions()) { + out.value(p.getUri().toString()); + } + out.endArray(); + } + }); + } + + private static void manageDocumentTree(Context context, Intent intent) { + Intent i = new Intent(context, SAFActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + ResultReturner.copyIntentExtras(intent, i); + context.startActivity(i); + } + + private static void writeDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + String uri = intent.getStringExtra("uri"); + if (uri == null) { + TermuxApiLogger.error("uri extra null"); + return; + } + DocumentFile f = DocumentFile.fromSingleUri(context, Uri.parse(uri)); + if (f == null) { + return; + } + writeDocumentFile(apiReceiver, context, intent, f); + } + + private static void createDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + String treeuri = intent.getStringExtra("treeuri"); + if (treeuri == null) { + TermuxApiLogger.error("treeuri extra null"); + return; + } + String mime = intent.getStringExtra("mimetype"); + if (mime == null) { + TermuxApiLogger.error("mimetype extra null"); + return; + } + String name = intent.getStringExtra("filename"); + if (name == null) { + TermuxApiLogger.error("filename extra null"); + return; + } + DocumentFile tree = DocumentFile.fromTreeUri(context, Uri.parse(treeuri)); + if (tree == null) { + return; + } + DocumentFile f = tree.createFile(mime, name); + if (f == null) { + return; + } + ResultReturner.returnData(apiReceiver, intent, out -> out.write(f.getUri().toString())); + } + + private static void readDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + String uri = intent.getStringExtra("uri"); + if (uri == null) { + TermuxApiLogger.error("uri extra null"); + return; + } + DocumentFile f = DocumentFile.fromSingleUri(context, Uri.parse(uri)); + if (f == null) { + return; + } + returnDocumentFile(apiReceiver, context, intent, f); + } + + private static void listDirectory(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + String treeuri = intent.getStringExtra("treeuri"); + if (treeuri == null) { + TermuxApiLogger.error("treeuri extra null"); + return; + } + DocumentFile tree = DocumentFile.fromSingleUri(context, Uri.parse(treeuri)); + if (tree == null) { + return; + } + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() + { + @Override + public void writeJson(JsonWriter out) throws Exception { + out.beginArray(); + try { + for (DocumentFile f : tree.listFiles()) { + statDocumentFile(out, f); + } + } catch (UnsupportedOperationException ignored) { } + out.endArray(); + } + }); + } + + private static void statURI(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + String uri = intent.getStringExtra("uri"); + if (uri == null) { + TermuxApiLogger.error("uri extra null"); + return; + } + DocumentFile f = DocumentFile.fromSingleUri(context, Uri.parse(uri)); + if (f == null) { + return; + } + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() + { + @Override + public void writeJson(JsonWriter out) throws Exception { + statDocumentFile(out, f); + } + }); + } + + + private static void statDocumentFile(JsonWriter out, DocumentFile f) throws Exception { + out.beginObject(); + out.name("name"); + out.value(f.getName()); + out.name("type"); + out.value(f.getType()); + out.name("uri"); + out.value(f.getUri().toString()); + if (! f.isDirectory()) { + out.name("length"); + out.value(f.length()); + } + out.endObject(); + } + + private static void removeDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + String uri = intent.getStringExtra("uri"); + if (uri == null) { + TermuxApiLogger.error("uri extra null"); + return; + } + DocumentFile f = DocumentFile.fromTreeUri(context, Uri.parse(uri)); + if (f == null) { + return; + } + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() + { + @Override + public void writeJson(JsonWriter out) throws Exception { + out.value(f.delete()); + } + }); + } + + private static void returnDocumentFile(TermuxApiReceiver apiReceiver, Context context, Intent intent, DocumentFile f) { + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.BinaryOutput() + { + @Override + public void writeResult(OutputStream out) throws Exception { + try (InputStream in = context.getContentResolver().openInputStream(f.getUri())) { + writeInputStreamToOutputStream(in, out); + } + } + }); + } + + private static void writeDocumentFile(TermuxApiReceiver apiReceiver, Context context, Intent intent, DocumentFile f) { + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.WithInput() + { + @Override + public void writeResult(PrintWriter unused) throws Exception { + try (OutputStream out = context.getContentResolver().openOutputStream(f.getUri(), "rwt")) { + writeInputStreamToOutputStream(in, out); + } + } + }); + } + + private static void writeInputStreamToOutputStream(InputStream in, OutputStream out) throws IOException { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + FileUtils.copy(in, out); + } + else { + byte[] buffer = new byte[4096]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + } + } + +} diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index fbfcee992..d68d3a162 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -208,6 +208,9 @@ private void doWork(Context context, Intent intent) { case "WifiEnable": WifiAPI.onReceiveWifiEnable(this, context, intent); break; + case "SAF": + SAFAPI.onReceive(this, context, intent); + break; default: TermuxApiLogger.error("Unrecognized 'api_method' extra: '" + apiMethod + "'"); } diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java index b50ca389d..921f68a16 100644 --- a/app/src/main/java/com/termux/api/util/ResultReturner.java +++ b/app/src/main/java/com/termux/api/util/ResultReturner.java @@ -13,6 +13,7 @@ import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; @@ -44,6 +45,27 @@ public void setInput(InputStream inputStream) throws Exception { this.in = inputStream; } } + + /** + * Possible subclass of {@link ResultWriter} when the output is binary data instead of text. + */ + public static abstract class BinaryOutput implements ResultWriter { + private OutputStream out; + + public void setOutput(OutputStream outputStream) { + this.out = outputStream; + } + + public abstract void writeResult(OutputStream out) throws Exception; + + /** + * writeResult with a PrintWriter is marked as final and overwritten, so you don't accidentally use it + */ + public final void writeResult(PrintWriter unused) throws Exception { + writeResult(out); + out.flush(); + } + } /** * Possible marker interface for a {@link ResultWriter} when input is to be read from stdin. @@ -122,6 +144,10 @@ public static void returnData(Object context, final Intent intent, final ResultW outputSocket.connect(new LocalSocketAddress(outputSocketAdress)); try (PrintWriter writer = new PrintWriter(outputSocket.getOutputStream())) { if (resultWriter != null) { + if (resultWriter instanceof BinaryOutput) { + BinaryOutput bout = (BinaryOutput) resultWriter; + bout.setOutput(outputSocket.getOutputStream()); + } if (resultWriter instanceof WithInput) { try (LocalSocket inputSocket = new LocalSocket()) { String inputSocketAdress = intent.getStringExtra(SOCKET_INPUT_EXTRA); From d9b9c8358e8bbaea8ba5a48161d18e1e2ba37b46 Mon Sep 17 00:00:00 2001 From: tareksander <57038324+tareksander@users.noreply.github.com> Date: Thu, 16 Dec 2021 16:59:41 +0100 Subject: [PATCH 002/142] Fixed: Subfolders in SAF. --- app/src/main/java/com/termux/api/SAFAPI.java | 124 +++++++++++-------- 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/termux/api/SAFAPI.java b/app/src/main/java/com/termux/api/SAFAPI.java index 9d8351e51..ad73f3f70 100644 --- a/app/src/main/java/com/termux/api/SAFAPI.java +++ b/app/src/main/java/com/termux/api/SAFAPI.java @@ -3,11 +3,14 @@ import android.content.Context; import android.content.Intent; import android.content.UriPermission; +import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.FileUtils; +import android.provider.DocumentsContract; import android.util.JsonWriter; +import android.webkit.MimeTypeMap; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -16,6 +19,7 @@ import com.termux.api.util.ResultReturner; import com.termux.api.util.TermuxApiLogger; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -132,30 +136,31 @@ private static void writeDocument(TermuxApiReceiver apiReceiver, Context context } private static void createDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { - String treeuri = intent.getStringExtra("treeuri"); - if (treeuri == null) { + String treeURIString = intent.getStringExtra("treeuri"); + if (treeURIString == null) { TermuxApiLogger.error("treeuri extra null"); return; } - String mime = intent.getStringExtra("mimetype"); - if (mime == null) { - TermuxApiLogger.error("mimetype extra null"); - return; - } String name = intent.getStringExtra("filename"); if (name == null) { TermuxApiLogger.error("filename extra null"); return; } - DocumentFile tree = DocumentFile.fromTreeUri(context, Uri.parse(treeuri)); - if (tree == null) { - return; + String mime = intent.getStringExtra("mimetype"); + if (mime == null) { + mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(name); } - DocumentFile f = tree.createFile(mime, name); - if (f == null) { - return; + if (mime == null) { + mime = "application/octet-stream"; } - ResultReturner.returnData(apiReceiver, intent, out -> out.write(f.getUri().toString())); + Uri treeURI = Uri.parse(treeURIString); + String id = DocumentsContract.getTreeDocumentId(treeURI); + try { + id = DocumentsContract.getDocumentId(Uri.parse(treeURIString)); + } catch (IllegalArgumentException ignored) {} + final String finalMime = mime; + final String finalId = id; + ResultReturner.returnData(apiReceiver, intent, out -> out.write(DocumentsContract.createDocument(context.getContentResolver(), DocumentsContract.buildDocumentUriUsingTree(treeURI, finalId), finalMime, name).toString())); } private static void readDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { @@ -172,23 +177,27 @@ private static void readDocument(TermuxApiReceiver apiReceiver, Context context, } private static void listDirectory(TermuxApiReceiver apiReceiver, Context context, Intent intent) { - String treeuri = intent.getStringExtra("treeuri"); - if (treeuri == null) { + String treeURIString = intent.getStringExtra("treeuri"); + if (treeURIString == null) { TermuxApiLogger.error("treeuri extra null"); return; } - DocumentFile tree = DocumentFile.fromSingleUri(context, Uri.parse(treeuri)); - if (tree == null) { - return; - } + Uri treeURI = Uri.parse(treeURIString); ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { out.beginArray(); + String id = DocumentsContract.getTreeDocumentId(treeURI); try { - for (DocumentFile f : tree.listFiles()) { - statDocumentFile(out, f); + id = DocumentsContract.getDocumentId(Uri.parse(treeURIString)); + } catch (IllegalArgumentException ignored) {} + try (Cursor c = context.getContentResolver().query(DocumentsContract.buildChildDocumentsUriUsingTree(Uri.parse(treeURIString), id), new String[] { + DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null)) { + while (c.moveToNext()) { + String documentId = c.getString(0); + Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(treeURI, documentId); + statDocument(out, context, documentUri); } } catch (UnsupportedOperationException ignored) { } out.endArray(); @@ -197,59 +206,68 @@ public void writeJson(JsonWriter out) throws Exception { } private static void statURI(TermuxApiReceiver apiReceiver, Context context, Intent intent) { - String uri = intent.getStringExtra("uri"); - if (uri == null) { + String uriString = intent.getStringExtra("uri"); + if (uriString == null) { TermuxApiLogger.error("uri extra null"); return; } - DocumentFile f = DocumentFile.fromSingleUri(context, Uri.parse(uri)); - if (f == null) { - return; - } + Uri uri = Uri.parse(uriString); + String id = DocumentsContract.getTreeDocumentId(Uri.parse(uriString)); + try { + id = DocumentsContract.getDocumentId(Uri.parse(uriString)); + } catch (IllegalArgumentException ignored) {} + Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri, id); ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { - statDocumentFile(out, f); + statDocument(out, context, Uri.parse(docUri.toString())); } }); } - private static void statDocumentFile(JsonWriter out, DocumentFile f) throws Exception { - out.beginObject(); - out.name("name"); - out.value(f.getName()); - out.name("type"); - out.value(f.getType()); - out.name("uri"); - out.value(f.getUri().toString()); - if (! f.isDirectory()) { - out.name("length"); - out.value(f.length()); - } - out.endObject(); - } - private static void removeDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { String uri = intent.getStringExtra("uri"); if (uri == null) { TermuxApiLogger.error("uri extra null"); return; } - DocumentFile f = DocumentFile.fromTreeUri(context, Uri.parse(uri)); - if (f == null) { - return; - } - ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() - { - @Override - public void writeJson(JsonWriter out) throws Exception { - out.value(f.delete()); + ResultReturner.returnData(apiReceiver, intent, out -> { + try { + if (DocumentsContract.deleteDocument(context.getContentResolver(), Uri.parse(uri))) { + out.print(0); + } else { + out.print(1); + } + } catch (FileNotFoundException | IllegalArgumentException e ) { + out.print(2); } }); } + private static void statDocument(JsonWriter out, Context context, Uri uri) throws Exception { + try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) { + if (c == null || c.getCount() == 0) { + return; + } + c.moveToNext(); + out.beginObject(); + out.name("name"); + out.value(c.getString(c.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME))); + out.name("type"); + String mime = c.getString(c.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE)); + out.value(mime); + out.name("uri"); + out.value(uri.toString()); + if (! DocumentsContract.Document.MIME_TYPE_DIR.equals(mime)) { + out.name("length"); + out.value(c.getInt(c.getColumnIndex(DocumentsContract.Document.COLUMN_SIZE))); + } + out.endObject(); + } + } + private static void returnDocumentFile(TermuxApiReceiver apiReceiver, Context context, Intent intent, DocumentFile f) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.BinaryOutput() { From d4e9c06b21bb6cc352ff01e8aad665e754dcb278 Mon Sep 17 00:00:00 2001 From: tareksander <57038324+tareksander@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:52:07 +0100 Subject: [PATCH 003/142] Changed: Changed the format for termux-saf-dirs to be like termux-saf-ls. --- app/src/main/java/com/termux/api/SAFAPI.java | 27 ++++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/termux/api/SAFAPI.java b/app/src/main/java/com/termux/api/SAFAPI.java index ad73f3f70..52dd8ee12 100644 --- a/app/src/main/java/com/termux/api/SAFAPI.java +++ b/app/src/main/java/com/termux/api/SAFAPI.java @@ -41,8 +41,10 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override protected void onDestroy() { super.onDestroy(); + finishAndRemoveTask(); if (! resultReturned) { ResultReturner.returnData(this, getIntent(), out -> out.write("")); + resultReturned = true; } } @@ -108,7 +110,7 @@ private static void getManagedDocumentTrees(TermuxApiReceiver apiReceiver, Conte public void writeJson(JsonWriter out) throws Exception { out.beginArray(); for (UriPermission p : context.getContentResolver().getPersistedUriPermissions()) { - out.value(p.getUri().toString()); + statDocument(out, context, treeUriToDocumentUri(p.getUri())); } out.endArray(); } @@ -147,9 +149,6 @@ private static void createDocument(TermuxApiReceiver apiReceiver, Context contex return; } String mime = intent.getStringExtra("mimetype"); - if (mime == null) { - mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(name); - } if (mime == null) { mime = "application/octet-stream"; } @@ -160,7 +159,9 @@ private static void createDocument(TermuxApiReceiver apiReceiver, Context contex } catch (IllegalArgumentException ignored) {} final String finalMime = mime; final String finalId = id; - ResultReturner.returnData(apiReceiver, intent, out -> out.write(DocumentsContract.createDocument(context.getContentResolver(), DocumentsContract.buildDocumentUriUsingTree(treeURI, finalId), finalMime, name).toString())); + ResultReturner.returnData(apiReceiver, intent, out -> + out.write(DocumentsContract.createDocument(context.getContentResolver(), DocumentsContract.buildDocumentUriUsingTree(treeURI, finalId), finalMime, name).toString()) + ); } private static void readDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { @@ -211,12 +212,7 @@ private static void statURI(TermuxApiReceiver apiReceiver, Context context, Inte TermuxApiLogger.error("uri extra null"); return; } - Uri uri = Uri.parse(uriString); - String id = DocumentsContract.getTreeDocumentId(Uri.parse(uriString)); - try { - id = DocumentsContract.getDocumentId(Uri.parse(uriString)); - } catch (IllegalArgumentException ignored) {} - Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri, id); + Uri docUri = treeUriToDocumentUri(Uri.parse(uriString)); ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override @@ -246,6 +242,15 @@ private static void removeDocument(TermuxApiReceiver apiReceiver, Context contex }); } + + private static Uri treeUriToDocumentUri(Uri tree) { + String id = DocumentsContract.getTreeDocumentId(tree); + try { + id = DocumentsContract.getDocumentId(tree); + } catch (IllegalArgumentException ignored) {} + return DocumentsContract.buildDocumentUriUsingTree(tree, id); + } + private static void statDocument(JsonWriter out, Context context, Uri uri) throws Exception { try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) { if (c == null || c.getCount() == 0) { From 5f12af478aff976673c852419faa4324aa60f5dd Mon Sep 17 00:00:00 2001 From: tareksander <57038324+tareksander@users.noreply.github.com> Date: Mon, 20 Dec 2021 22:01:12 +0100 Subject: [PATCH 004/142] Added: Option the specify the channel for termux-notification and an interface to create and delete channels. --- .../java/com/termux/api/NotificationAPI.java | 83 ++++++++++++++----- .../com/termux/api/TermuxApiReceiver.java | 5 +- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/termux/api/NotificationAPI.java b/app/src/main/java/com/termux/api/NotificationAPI.java index d407cc243..98664f092 100644 --- a/app/src/main/java/com/termux/api/NotificationAPI.java +++ b/app/src/main/java/com/termux/api/NotificationAPI.java @@ -65,27 +65,9 @@ public void writeResult(PrintWriter out) { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - String priorityExtra = intent.getStringExtra("priority"); - if (priorityExtra == null) priorityExtra = "default"; - int importance; - switch (priorityExtra) { - case "high": - case "max": - importance = NotificationManager.IMPORTANCE_HIGH; - break; - case "low": - importance = NotificationManager.IMPORTANCE_LOW; - break; - case "min": - importance = NotificationManager.IMPORTANCE_MIN; - break; - default: - importance = NotificationManager.IMPORTANCE_DEFAULT; - } NotificationChannel channel = new NotificationChannel(CHANNEL_ID, - CHANNEL_TITLE, importance); + CHANNEL_TITLE, priorityFromIntent(intent)); manager.createNotificationChannel(channel); - notification.setChannelId(CHANNEL_ID); } manager.notify(notificationId, 0, notification.build()); @@ -93,6 +75,60 @@ public void writeResult(PrintWriter out) { }); } + public static void onReceiveChannel(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + NotificationManager m = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + String channelId = intent.getStringExtra("id"); + String channelName = intent.getStringExtra("name"); + + if (channelId == null || channelId.equals("")) { + ResultReturner.returnData(apiReceiver, intent, out -> out.println("Channel id not specified.")); + return; + } + + if (intent.getBooleanExtra("delete",false)) { + m.deleteNotificationChannel(channelId); + ResultReturner.returnData(apiReceiver, intent, out -> out.println("Deleted channel with id \""+channelId+"\".")); + return; + } + + if (channelName == null || channelName.equals("")) { + ResultReturner.returnData(apiReceiver, intent, out -> out.println("Cannot create a channel without a name.")); + } + + NotificationChannel c = new NotificationChannel(channelId, channelName, priorityFromIntent(intent)); + m.createNotificationChannel(c); + ResultReturner.returnData(apiReceiver, intent, out -> out.println("Created channel with id \""+channelId+"\" and name \""+channelName+"\".")); + } catch (Exception e) { + e.printStackTrace(); + ResultReturner.returnData(apiReceiver, intent, out -> out.println("Could not create/delete channel.")); + } + } else { + ResultReturner.returnData(apiReceiver, intent, out -> out.println("Notification channels are only available on Android 8.0 and higher, use the options for termux-notification instead.")); + } + } + + private static int priorityFromIntent(Intent intent) { + String priorityExtra = intent.getStringExtra("priority"); + if (priorityExtra == null) priorityExtra = "default"; + int importance; + switch (priorityExtra) { + case "high": + case "max": + importance = NotificationManager.IMPORTANCE_HIGH; + break; + case "low": + importance = NotificationManager.IMPORTANCE_LOW; + break; + case "min": + importance = NotificationManager.IMPORTANCE_MIN; + break; + default: + importance = NotificationManager.IMPORTANCE_DEFAULT; + } + return importance; + } static Pair buildNotification(final Context context, final Intent intent) { String priorityExtra = intent.getStringExtra("priority"); @@ -142,9 +178,14 @@ static Pair buildNotification(final Context final String notificationId = getNotificationId(intent); String groupKey = intent.getStringExtra("group"); - + + String channel = intent.getStringExtra("channel"); + if (channel == null) { + channel = CHANNEL_ID; + } + final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, - CHANNEL_ID); + channel); notification.setSmallIcon(R.drawable.ic_event_note_black_24dp); notification.setColor(0xFF000000); notification.setContentTitle(title); diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index fbfcee992..6ceb5b4ce 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -5,8 +5,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.nfc.NfcAdapter; -import android.os.Build; import android.provider.Settings; import android.widget.Toast; @@ -131,6 +129,9 @@ private void doWork(Context context, Intent intent) { case "Notification": NotificationAPI.onReceiveShowNotification(this, context, intent); break; + case "NotificationChannel": + NotificationAPI.onReceiveChannel(this, context, intent); + break; case "NotificationRemove": NotificationAPI.onReceiveRemoveNotification(this, context, intent); break; From 22e81711c68bf0295b70719f5b202f9dc7069f3e Mon Sep 17 00:00:00 2001 From: tareksander <57038324+tareksander@users.noreply.github.com> Date: Sat, 25 Dec 2021 18:17:17 +0100 Subject: [PATCH 005/142] Changed: Write newline after the URI for manageDocumentTree, createDocument and readDocument. --- app/src/main/java/com/termux/api/SAFAPI.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/termux/api/SAFAPI.java b/app/src/main/java/com/termux/api/SAFAPI.java index 52dd8ee12..c1d8e0d45 100644 --- a/app/src/main/java/com/termux/api/SAFAPI.java +++ b/app/src/main/java/com/termux/api/SAFAPI.java @@ -56,7 +56,7 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten if (uri != null) { getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); resultReturned = true; - ResultReturner.returnData(this, getIntent(), out -> out.write(data.getDataString())); + ResultReturner.returnData(this, getIntent(), out -> out.println(data.getDataString())); } } finish(); @@ -160,7 +160,7 @@ private static void createDocument(TermuxApiReceiver apiReceiver, Context contex final String finalMime = mime; final String finalId = id; ResultReturner.returnData(apiReceiver, intent, out -> - out.write(DocumentsContract.createDocument(context.getContentResolver(), DocumentsContract.buildDocumentUriUsingTree(treeURI, finalId), finalMime, name).toString()) + out.println(DocumentsContract.createDocument(context.getContentResolver(), DocumentsContract.buildDocumentUriUsingTree(treeURI, finalId), finalMime, name).toString()) ); } @@ -232,12 +232,12 @@ private static void removeDocument(TermuxApiReceiver apiReceiver, Context contex ResultReturner.returnData(apiReceiver, intent, out -> { try { if (DocumentsContract.deleteDocument(context.getContentResolver(), Uri.parse(uri))) { - out.print(0); + out.println(0); } else { - out.print(1); + out.println(1); } } catch (FileNotFoundException | IllegalArgumentException e ) { - out.print(2); + out.println(2); } }); } From e315b53ec048e3dd321d69525c9e1162ca53f19f Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 31 Dec 2021 16:19:45 +0500 Subject: [PATCH 006/142] Bump to v0.50.1 Bumping to re-release 0.50.0 with semantic versioned github release tag since APK attachment to release failed --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1598fb575..758bbd9ce 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "com.termux.api" minSdkVersion project.properties.minSdkVersion.toInteger() targetSdkVersion project.properties.targetSdkVersion.toInteger() - versionCode 50 - versionName "0.50.0" + versionCode 51 + versionName "0.50.1" if (appVersionName) versionName = appVersionName validateVersionName(versionName) From c2754cafcf8de09a314fb6c5ca97264903c2ed20 Mon Sep 17 00:00:00 2001 From: Henrik Grimler Date: Mon, 3 Jan 2022 11:55:42 +0100 Subject: [PATCH 007/142] bug report template: ask for termux-api package version as well --- .github/ISSUE_TEMPLATE/bug_report.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d370b695b..305cac117 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -30,6 +30,7 @@ A clear and concise description of what you expected to happen. **Additional information** -* Termux application version: +* termux-api application version: +* termux-api package version (installed through apt): * Android OS version: * Device model: From a4fbba0ba445969a4aa120f2aa07d3fa03be7b04 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 8 Jan 2022 14:47:52 +0500 Subject: [PATCH 008/142] Changed: Refactor attach_debug_apk_to_release workflow add support for sha256sums --- .../workflows/attach_debug_apk_to_release.yml | 64 +++++++++++++++++++ .../attach_debug_apks_to_release.yml | 36 ----------- 2 files changed, 64 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/attach_debug_apk_to_release.yml delete mode 100644 .github/workflows/attach_debug_apks_to_release.yml diff --git a/.github/workflows/attach_debug_apk_to_release.yml b/.github/workflows/attach_debug_apk_to_release.yml new file mode 100644 index 000000000..50e2d62de --- /dev/null +++ b/.github/workflows/attach_debug_apk_to_release.yml @@ -0,0 +1,64 @@ +name: Attach Debug APK To Release + +on: + release: + types: + - published + +jobs: + attach-apks: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Clone repository + uses: actions/checkout@v2 + with: + ref: ${{ env.GITHUB_REF }} + + - name: Build and attach APK to release + shell: bash {0} + run: | + exit_on_error() { + echo "$1" + echo "Deleting '$RELEASE_VERSION_NAME' release and '$GITHUB_REF' tag" + hub release delete "$RELEASE_VERSION_NAME" + git push --delete origin "$GITHUB_REF" + exit 1 + } + + echo "Setting vars" + RELEASE_VERSION_NAME="${GITHUB_REF/refs\/tags\//}" + if ! printf "%s" "${RELEASE_VERSION_NAME/v/}" | grep -qP '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'; then + exit_on_error "The versionName '${RELEASE_VERSION_NAME/v/}' is not a valid version as per semantic version '2.0.0' spec in the format 'major.minor.patch(-prerelease)(+buildmetadata)'. https://semver.org/spec/v2.0.0.html." + fi + + APK_DIR_PATH="./app/build/outputs/apk/debug" + APK_VERSION_TAG="$RELEASE_VERSION_NAME+github-debug" + APK_BASENAME_PREFIX="termux-api_$APK_VERSION_TAG" + + echo "Building APK for '$RELEASE_VERSION_NAME' release" + export TERMUX_API_APK_VERSION_TAG="$APK_VERSION_TAG" # Used by app/build.gradle + if ! ./gradlew assembleDebug; then + exit_on_error "Build failed for '$RELEASE_VERSION_NAME' release." + fi + + echo "Validating APK" + if ! test -f "$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk"; then + files_found="$(ls "$APK_DIR_PATH")" + exit_on_error "Failed to find built APK at '$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk'. Files found: "$'\n'"$files_found" + fi + + echo "Generating sha25sums file" + if ! (cd "$APK_DIR_PATH"; sha256sum "${APK_BASENAME_PREFIX}.apk" > sha256sums); then + exit_on_error "Generate sha25sums failed for '$RELEASE_VERSION_NAME' release." + fi + + echo "Attaching APK to github release" + if ! hub release edit \ + -m "" \ + -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk" \ + -a "$APK_DIR_PATH/sha256sums" \ + "$RELEASE_VERSION_NAME"; then + exit_on_error "Attach APK to release failed for '$RELEASE_VERSION_NAME' release." + fi diff --git a/.github/workflows/attach_debug_apks_to_release.yml b/.github/workflows/attach_debug_apks_to_release.yml deleted file mode 100644 index 05540dcf0..000000000 --- a/.github/workflows/attach_debug_apks_to_release.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Attach Debug APKs To Release - -on: - release: - types: - - published - -jobs: - attach-apks: - runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Clone repository - uses: actions/checkout@v2 - with: - ref: ${{ env.GITHUB_REF }} - - name: Set vars - run: | - RELEASE_VERSION_NAME="${GITHUB_REF/refs\/tags\//}" - if ! printf "%s" "${RELEASE_VERSION_NAME/v/}" | grep -qP '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'; then - echo "The versionName '${RELEASE_VERSION_NAME/v/}' is not a valid version as per semantic version '2.0.0' spec in the format 'major.minor.patch(-prerelease)(+buildmetadata)'. https://semver.org/spec/v2.0.0.html." - exit 1 - fi - echo "TERMUX_API_RELEASE_TAG=$RELEASE_VERSION_NAME" >> $GITHUB_ENV - echo "TERMUX_API_APK_VERSION_TAG=$RELEASE_VERSION_NAME+github-debug" >> $GITHUB_ENV # Used by app/build.gradle - - name: Echo release - run: echo "Attaching debug APK to '${{ env.TERMUX_API_RELEASE_TAG }}' release" - - name: Build - run: ./gradlew assembleDebug - - name: Attach APKs to release - run: >- - hub release edit - -m "" - -a "./app/build/outputs/apk/debug/termux-api_${{ env.TERMUX_API_APK_VERSION_TAG }}.apk" - "${{ env.TERMUX_API_RELEASE_TAG }}" From a68ebbea84f1bad3517b2c29dc71605849fd272d Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 8 Jan 2022 14:50:28 +0500 Subject: [PATCH 009/142] Changed|Fixed: Fix and refactor debug_build workflow add support for sha256sums Copy & paste typo for TERMUX_WIDGET instead of TERMUX_API was preventing APK to be attached to debug build workflow. --- .github/workflows/debug_build.yml | 65 ++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/.github/workflows/debug_build.yml b/.github/workflows/debug_build.yml index 239a65dde..04a24beb6 100644 --- a/.github/workflows/debug_build.yml +++ b/.github/workflows/debug_build.yml @@ -1,6 +1,12 @@ name: Build -on: push +on: + push: + branches: + - master + pull_request: + branches: + - master jobs: build: @@ -8,22 +14,53 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v2 - - name: Set vars + + - name: Build APK + shell: bash {0} run: | - # Set NEW_VERSION_NAME to "+" + exit_on_error() { echo "$1"; exit 1; } + + echo "Setting vars" + # Set RELEASE_VERSION_NAME to "+" CURRENT_VERSION_NAME_REGEX='\s+versionName "([^"]+)"$' CURRENT_VERSION_NAME="$(grep -m 1 -E "$CURRENT_VERSION_NAME_REGEX" ./app/build.gradle | sed -r "s/$CURRENT_VERSION_NAME_REGEX/\1/")" - NEW_VERSION_NAME="$CURRENT_VERSION_NAME+${GITHUB_SHA:0:7}" - echo "TERMUX_WIDGET_APP_VERSION_NAME=$NEW_VERSION_NAME" >> $GITHUB_ENV # Used by app/build.gradle - echo "TERMUX_WIDGET_APK_VERSION_TAG=v$NEW_VERSION_NAME-github-debug" >> $GITHUB_ENV # Used by app/build.gradle - - name: Echo version - run: echo "Building APK for '$TERMUX_WIDGET_APP_VERSION_NAME'" - - name: Build - run: ./gradlew assembleDebug - - name: Store generated APK file + RELEASE_VERSION_NAME="v$CURRENT_VERSION_NAME+${GITHUB_SHA:0:7}" # The "+" is necessary so that versioning precedence is not affected + if ! printf "%s" "${RELEASE_VERSION_NAME/v/}" | grep -qP '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'; then + exit_on_error "The versionName '${RELEASE_VERSION_NAME/v/}' is not a valid version as per semantic version '2.0.0' spec in the format 'major.minor.patch(-prerelease)(+buildmetadata)'. https://semver.org/spec/v2.0.0.html." + fi + + APK_DIR_PATH="./app/build/outputs/apk/debug" + APK_VERSION_TAG="$RELEASE_VERSION_NAME-github-debug" # Note the "-", GITHUB_SHA will already have "+" before it + APK_BASENAME_PREFIX="termux-api_$APK_VERSION_TAG" + + # Used by attachment steps later + echo "APK_DIR_PATH=$APK_DIR_PATH" >> $GITHUB_ENV + echo "APK_VERSION_TAG=$APK_VERSION_TAG" >> $GITHUB_ENV + echo "APK_BASENAME_PREFIX=$APK_BASENAME_PREFIX" >> $GITHUB_ENV + + echo "Building APK for '$RELEASE_VERSION_NAME' build" + export TERMUX_API_APP_VERSION_NAME="${RELEASE_VERSION_NAME/v/}" # Used by app/build.gradle + export TERMUX_API_APK_VERSION_TAG="$APK_VERSION_TAG" # Used by app/build.gradle + if ! ./gradlew assembleDebug; then + exit_on_error "Build failed for '$RELEASE_VERSION_NAME' build." + fi + + echo "Validating APK" + if ! test -f "$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk"; then + files_found="$(ls "$APK_DIR_PATH")" + exit_on_error "Failed to find built APK at '$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk'. Files found: "$'\n'"$files_found" + fi + + echo "Generating sha25sums file" + if ! (cd "$APK_DIR_PATH"; sha256sum "${APK_BASENAME_PREFIX}.apk" > sha256sums); then + exit_on_error "Generate sha25sums failed for '$RELEASE_VERSION_NAME' release." + fi + + - name: Attach files uses: actions/upload-artifact@v2 with: - name: termux-api + name: ${{ env.APK_BASENAME_PREFIX }} path: | - ./app/build/outputs/apk/debug/termux-api_${{ env.TERMUX_WIDGET_APK_VERSION_TAG }}.apk - ./app/build/outputs/apk/debug/output-metadata.json + ${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}.apk + ${{ env.APK_DIR_PATH }}/sha256sums + ${{ env.APK_DIR_PATH }}/output-metadata.json From e2bb7a80b7a5986a233f26ac4c40716c01645806 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 8 Jan 2022 20:01:51 +0500 Subject: [PATCH 010/142] Changed: Remove termux-emulator JNI libs from APK --- app/build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 758bbd9ce..68927ec84 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,8 +56,9 @@ android { } } - lintOptions { - disable 'ExpiredTargetSdkVersion' + packagingOptions { + // Remove terminal-emulator JNI libs added via termux-shared dependency + exclude 'lib/*/libtermux.so' } } From c7abdebd8b878c298c4fbacac6ffc01c02e33167 Mon Sep 17 00:00:00 2001 From: tareksander <57038324+tareksander@users.noreply.github.com> Date: Fri, 14 Jan 2022 19:38:34 +0100 Subject: [PATCH 011/142] Changed: Renamed App to SocketListener and call that from the real Application class. --- app/src/main/java/com/termux/api/App.java | 223 ----------------- .../java/com/termux/api/SocketListener.java | 233 ++++++++++++++++++ .../com/termux/api/TermuxAPIApplication.java | 2 + 3 files changed, 235 insertions(+), 223 deletions(-) delete mode 100644 app/src/main/java/com/termux/api/App.java create mode 100644 app/src/main/java/com/termux/api/SocketListener.java diff --git a/app/src/main/java/com/termux/api/App.java b/app/src/main/java/com/termux/api/App.java deleted file mode 100644 index 82b85b0be..000000000 --- a/app/src/main/java/com/termux/api/App.java +++ /dev/null @@ -1,223 +0,0 @@ -package com.termux.api; - -import android.app.Application; -import android.content.Intent; -import android.net.LocalServerSocket; -import android.net.LocalSocket; - -import com.termux.api.util.TermuxApiLogger; - -import java.io.BufferedWriter; -import java.io.DataInputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class App extends Application -{ - public static final String LISTEN_ADDRESS = "com.termux.api://listen"; - private static final Pattern EXTRA_STRING = Pattern.compile("(-e|--es|--esa) +([^ ]+) +\"(.*?)(? { - try (LocalServerSocket listen = new LocalServerSocket(LISTEN_ADDRESS)) { - while (true) { - try (LocalSocket con = listen.accept(); - DataInputStream in = new DataInputStream(con.getInputStream()); - BufferedWriter out = new BufferedWriter(new OutputStreamWriter(con.getOutputStream()))) { - // only accept connections from Termux programs - if (con.getPeerCredentials().getUid() != getApplicationInfo().uid) { - continue; - } - try { - //System.out.println("connection"); - int length = in.readUnsignedShort(); - byte[] b = new byte[length]; - in.readFully(b); - String cmdline = new String(b, StandardCharsets.UTF_8); - - Intent intent = new Intent(getApplicationContext(), TermuxApiReceiver.class); - //System.out.println(cmdline.replaceAll("--es socket_input \".*?\"","").replaceAll("--es socket_output \".*?\"","")); - HashMap stringExtras = new HashMap<>(); - HashMap stringArrayExtras = new HashMap<>(); - HashMap booleanExtras = new HashMap<>(); - HashMap intExtras = new HashMap<>(); - HashMap floatExtras = new HashMap<>(); - HashMap intArrayExtras = new HashMap<>(); - HashMap longArrayExtras = new HashMap<>(); - boolean err = false; - - // extract and remove the string extras first, so another argument embedded in a string isn't counted as an argument - Matcher m = EXTRA_STRING.matcher(cmdline); - while (m.find()) { - String option = m.group(1); - if ("-e".equals(option) || "--es".equals(option)) { - // unescape " - stringExtras.put(m.group(2), Objects.requireNonNull(m.group(3)).replaceAll("\\\\\"","\"")); - } else { - // split the list - String[] list = Objects.requireNonNull(m.group(3)).split("(? e : stringExtras.entrySet()) { - intent.putExtra(e.getKey(), e.getValue()); - } - for (Map.Entry e : stringArrayExtras.entrySet()) { - intent.putExtra(e.getKey(), e.getValue()); - } - for (Map.Entry e : intExtras.entrySet()) { - intent.putExtra(e.getKey(), e.getValue()); - } - for (Map.Entry e : booleanExtras.entrySet()) { - intent.putExtra(e.getKey(), e.getValue()); - } - for (Map.Entry e : floatExtras.entrySet()) { - intent.putExtra(e.getKey(), e.getValue()); - } - for (Map.Entry e : intArrayExtras.entrySet()) { - intent.putExtra(e.getKey(), e.getValue()); - } - for (Map.Entry e : longArrayExtras.entrySet()) { - intent.putExtra(e.getKey(), e.getValue()); - } - getApplicationContext().sendOrderedBroadcast(intent, null); - // send a null byte as a sign that the arguments have been successfully received, parsed and the broadcast receiver is called - con.getOutputStream().write(0); - con.getOutputStream().flush(); - } catch (Exception e) { - TermuxApiLogger.error("Error parsing arguments", e); - out.write("Exception in the plugin\n"); - out.flush(); - } - } - } - } catch (Exception e) { - TermuxApiLogger.error("Error listening for connections", e); - } - }).start(); - } - -} diff --git a/app/src/main/java/com/termux/api/SocketListener.java b/app/src/main/java/com/termux/api/SocketListener.java new file mode 100644 index 000000000..3da329a73 --- /dev/null +++ b/app/src/main/java/com/termux/api/SocketListener.java @@ -0,0 +1,233 @@ +package com.termux.api; + +import android.app.Application; +import android.content.Intent; +import android.net.LocalServerSocket; +import android.net.LocalSocket; + +import com.termux.api.util.TermuxApiLogger; + +import java.io.BufferedWriter; +import java.io.DataInputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SocketListener +{ + public static final String LISTEN_ADDRESS = "com.termux.api://listen"; + private static final Pattern EXTRA_STRING = Pattern.compile("(-e|--es|--esa) +([^ ]+) +\"(.*?)(? { + try (LocalServerSocket listen = new LocalServerSocket(LISTEN_ADDRESS)) { + while (true) { + try (LocalSocket con = listen.accept(); + DataInputStream in = new DataInputStream(con.getInputStream()); + BufferedWriter out = new BufferedWriter(new OutputStreamWriter(con.getOutputStream()))) { + // only accept connections from Termux programs + if (con.getPeerCredentials().getUid() != app.getApplicationInfo().uid) { + continue; + } + try { + //System.out.println("connection"); + int length = in.readUnsignedShort(); + byte[] b = new byte[length]; + in.readFully(b); + String cmdline = new String(b, StandardCharsets.UTF_8); + + Intent intent = new Intent(app.getApplicationContext(), TermuxApiReceiver.class); + //System.out.println(cmdline.replaceAll("--es socket_input \".*?\"","").replaceAll("--es socket_output \".*?\"","")); + HashMap stringExtras = new HashMap<>(); + HashMap stringArrayExtras = new HashMap<>(); + HashMap booleanExtras = new HashMap<>(); + HashMap intExtras = new HashMap<>(); + HashMap floatExtras = new HashMap<>(); + HashMap intArrayExtras = new HashMap<>(); + HashMap longArrayExtras = new HashMap<>(); + boolean err = false; + + // extract and remove the string extras first, so another argument embedded in a string isn't counted as an argument + Matcher m = EXTRA_STRING.matcher(cmdline); + while (m.find()) { + String option = m.group(1); + if ("-e".equals(option) || "--es".equals(option)) { + // unescape " + stringExtras.put(m.group(2), Objects.requireNonNull(m.group(3)).replaceAll("\\\\\"", "\"")); + } + else { + // split the list + String[] list = Objects.requireNonNull(m.group(3)).split("(? e : stringExtras.entrySet()) { + intent.putExtra(e.getKey(), e.getValue()); + } + for (Map.Entry e : stringArrayExtras.entrySet()) { + intent.putExtra(e.getKey(), e.getValue()); + } + for (Map.Entry e : intExtras.entrySet()) { + intent.putExtra(e.getKey(), e.getValue()); + } + for (Map.Entry e : booleanExtras.entrySet()) { + intent.putExtra(e.getKey(), e.getValue()); + } + for (Map.Entry e : floatExtras.entrySet()) { + intent.putExtra(e.getKey(), e.getValue()); + } + for (Map.Entry e : intArrayExtras.entrySet()) { + intent.putExtra(e.getKey(), e.getValue()); + } + for (Map.Entry e : longArrayExtras.entrySet()) { + intent.putExtra(e.getKey(), e.getValue()); + } + app.getApplicationContext().sendOrderedBroadcast(intent, null); + // send a null byte as a sign that the arguments have been successfully received, parsed and the broadcast receiver is called + con.getOutputStream().write(0); + con.getOutputStream().flush(); + } + catch (Exception e) { + TermuxApiLogger.error("Error parsing arguments", e); + out.write("Exception in the plugin\n"); + out.flush(); + } + } + } + } + catch (Exception e) { + TermuxApiLogger.error("Error listening for connections", e); + } + }); + listener.start(); + } + } + +} diff --git a/app/src/main/java/com/termux/api/TermuxAPIApplication.java b/app/src/main/java/com/termux/api/TermuxAPIApplication.java index 2184070eb..29e0e0dea 100644 --- a/app/src/main/java/com/termux/api/TermuxAPIApplication.java +++ b/app/src/main/java/com/termux/api/TermuxAPIApplication.java @@ -19,6 +19,8 @@ public void onCreate() { // Set log config for the app setLogLevel(getApplicationContext(), true); + SocketListener.createSocketListener(this); + Logger.logDebug("Starting Application"); } From a8574836511f13686e5322fa002f94df0eb3e851 Mon Sep 17 00:00:00 2001 From: Wzaw1 <78591480+wzaw1@users.noreply.github.com> Date: Sun, 6 Feb 2022 20:52:47 +0100 Subject: [PATCH 012/142] CallLogAPI: Add dual sim feature --- app/src/main/java/com/termux/api/CallLogAPI.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/termux/api/CallLogAPI.java b/app/src/main/java/com/termux/api/CallLogAPI.java index cf7232f63..5e69f27bd 100644 --- a/app/src/main/java/com/termux/api/CallLogAPI.java +++ b/app/src/main/java/com/termux/api/CallLogAPI.java @@ -44,6 +44,7 @@ private static void getCallLogs(Context context, JsonWriter out, int offset, int int dateIndex = cur.getColumnIndex(CallLog.Calls.DATE); int durationIndex = cur.getColumnIndex(CallLog.Calls.DURATION); int callTypeIndex = cur.getColumnIndex(CallLog.Calls.TYPE); + int simTypeIndex = cur.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); out.beginArray(); @@ -56,6 +57,7 @@ private static void getCallLogs(Context context, JsonWriter out, int offset, int out.name("type").value(getCallTypeString(cur.getInt(callTypeIndex))); out.name("date").value(getDateString(cur.getLong(dateIndex), dateFormat)); out.name("duration").value(getTimeString(cur.getInt(durationIndex))); + out.name("sim_id").value(cur.getString(simTypeIndex)); cur.moveToPrevious(); out.endObject(); From dc89cd69ef9c32d8cfa8909c9690a9024ace3149 Mon Sep 17 00:00:00 2001 From: Jatin Desai Date: Tue, 28 Dec 2021 14:04:15 +0000 Subject: [PATCH 013/142] TelephonyAPI: add 5G NR radio support and additional fields for LTE --- .../java/com/termux/api/TelephonyAPI.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/app/src/main/java/com/termux/api/TelephonyAPI.java b/app/src/main/java/com/termux/api/TelephonyAPI.java index f44dcea43..ee23aa633 100644 --- a/app/src/main/java/com/termux/api/TelephonyAPI.java +++ b/app/src/main/java/com/termux/api/TelephonyAPI.java @@ -10,6 +10,10 @@ import android.telephony.CellInfoGsm; import android.telephony.CellInfoLte; import android.telephony.CellInfoWcdma; +import android.telephony.CellInfoNr; +import android.telephony.CellIdentityNr; +import android.telephony.CellSignalStrength; +import android.telephony.CellSignalStrengthNr; import android.telephony.TelephonyManager; import android.util.JsonWriter; import android.util.Log; @@ -28,6 +32,18 @@ public class TelephonyAPI { private static void writeIfKnown(JsonWriter out, String name, int value) throws IOException { if (value != Integer.MAX_VALUE) out.name(name).value(value); } + private static void writeIfKnown(JsonWriter out, String name, long value) throws IOException { + if (value != Long.MAX_VALUE) out.name(name).value(value); + } + private static void writeIfKnown(JsonWriter out, String name, int value[]) throws IOException { + if (value != null) { + out.name(name); + out.beginArray(); + for (int i = 0; i < value.length; i++) out.value(value[i]); + out.endArray(); + + } + } static void onReceiveTelephonyCellInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @@ -76,6 +92,40 @@ public void writeJson(JsonWriter out) throws Exception { writeIfKnown(out, "tac", lteInfo.getCellIdentity().getTac()); writeIfKnown(out, "mcc", lteInfo.getCellIdentity().getMcc()); writeIfKnown(out, "mnc", lteInfo.getCellIdentity().getMnc()); + + writeIfKnown(out, "rsrp", lteInfo.getCellSignalStrength().getRsrp()); + writeIfKnown(out, "rsrq", lteInfo.getCellSignalStrength().getRsrq()); + writeIfKnown(out, "rssi", lteInfo.getCellSignalStrength().getRssi()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + writeIfKnown(out, "bands", lteInfo.getCellIdentity().getBands()); + } + } else if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) && (cellInfo instanceof CellInfoNr)) { + CellInfoNr nrInfo = (CellInfoNr) cellInfo; + CellIdentityNr nrcellIdent = (CellIdentityNr) nrInfo.getCellIdentity(); + CellSignalStrength ssInfo = nrInfo.getCellSignalStrength(); + out.name("type").value("nr"); + out.name("registered").value(cellInfo.isRegistered()); + + out.name("asu").value(ssInfo.getAsuLevel()); + out.name("dbm").value(ssInfo.getDbm()); + writeIfKnown(out, "level", ssInfo.getLevel()); + writeIfKnown(out, "nci", nrcellIdent.getNci()); + writeIfKnown(out, "pci", nrcellIdent.getPci()); + writeIfKnown(out, "tac", nrcellIdent.getTac()); + out.name("mcc").value(nrcellIdent.getMccString()); + out.name("mnc").value(nrcellIdent.getMncString()); + if (ssInfo instanceof CellSignalStrengthNr) { + CellSignalStrengthNr nrssInfo = (CellSignalStrengthNr) ssInfo; + writeIfKnown(out, "csi_rsrp", nrssInfo.getCsiRsrp()); + writeIfKnown(out, "csi_rsrq", nrssInfo.getCsiRsrq()); + writeIfKnown(out, "csi_sinr", nrssInfo.getCsiSinr()); + writeIfKnown(out, "ss_rsrp", nrssInfo.getSsRsrp()); + writeIfKnown(out, "ss_rsrq", nrssInfo.getSsRsrq()); + writeIfKnown(out, "ss_sinr", nrssInfo.getSsSinr()); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + writeIfKnown(out, "bands", nrcellIdent.getBands()); + } } else if (cellInfo instanceof CellInfoCdma) { CellInfoCdma cdmaInfo = (CellInfoCdma) cellInfo; out.name("type").value("cdma"); @@ -269,6 +319,10 @@ public void writeJson(JsonWriter out) throws Exception { networkTypeName = "unknown"; break; default: + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) && (networkType == TelephonyManager.NETWORK_TYPE_NR)) { + networkTypeName = "nr"; + break; + } networkTypeName = Integer.toString(networkType); break; } From f5ae1afcc6f1edba25f992b49886ec726b92ebbd Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 11 Mar 2022 20:34:51 +0500 Subject: [PATCH 014/142] Changed: Bump termux-shared to f3ffc36bfd --- app/build.gradle | 7 ++++++- app/src/main/java/com/termux/api/TermuxAPIApplication.java | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 68927ec84..1edb5c975 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -66,7 +66,12 @@ dependencies { implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.biometric:biometric:1.2.0-alpha03' implementation 'androidx.media:media:1.4.3' - implementation 'com.termux.termux-app:termux-shared:f3ffc36bfd' + + implementation 'com.termux.termux-app:termux-shared:760ae78aff' + // Use if below libraries are published locally by termux-app with `./gradlew publishReleasePublicationToMavenLocal` and used with `mavenLocal()`. + // If updates are done, republish there and sync project with gradle files here + // https://github.com/termux/termux-app/wiki/Termux-Libraries + // implementation 'com.termux:termux-shared:0.118.0' } task versionName { diff --git a/app/src/main/java/com/termux/api/TermuxAPIApplication.java b/app/src/main/java/com/termux/api/TermuxAPIApplication.java index 29e0e0dea..a70276cdf 100644 --- a/app/src/main/java/com/termux/api/TermuxAPIApplication.java +++ b/app/src/main/java/com/termux/api/TermuxAPIApplication.java @@ -3,9 +3,9 @@ import android.app.Application; import android.content.Context; -import com.termux.shared.crash.TermuxCrashUtils; import com.termux.shared.logger.Logger; -import com.termux.shared.settings.preferences.TermuxAPIAppSharedPreferences; +import com.termux.shared.termux.crash.TermuxCrashUtils; +import com.termux.shared.termux.settings.preferences.TermuxAPIAppSharedPreferences; public class TermuxAPIApplication extends Application { From 7ef6b20609454cdc0e7315a550078b0a3262a006 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 11 Mar 2022 20:39:41 +0500 Subject: [PATCH 015/142] Added: Request ACCESS_BACKGROUND_LOCATION as required by android 10+ https://developer.android.com/training/location/permissions#request-background-location --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cea78e993..c2812d26b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ + From 5a8f817d5807d324a9f3a36b760d84643e1a1046 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 11 Mar 2022 21:01:25 +0500 Subject: [PATCH 016/142] Changed: Set DEFAULT_LOG_TAG at application startup as per termux/termux-app@1b794b35 and termux/termux-app@6ff55729 --- app/src/main/java/com/termux/api/TermuxAPIApplication.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/termux/api/TermuxAPIApplication.java b/app/src/main/java/com/termux/api/TermuxAPIApplication.java index a70276cdf..44433b449 100644 --- a/app/src/main/java/com/termux/api/TermuxAPIApplication.java +++ b/app/src/main/java/com/termux/api/TermuxAPIApplication.java @@ -4,6 +4,7 @@ import android.content.Context; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.crash.TermuxCrashUtils; import com.termux.shared.termux.settings.preferences.TermuxAPIAppSharedPreferences; @@ -19,12 +20,14 @@ public void onCreate() { // Set log config for the app setLogLevel(getApplicationContext(), true); - SocketListener.createSocketListener(this); - Logger.logDebug("Starting Application"); + + SocketListener.createSocketListener(this); } public static void setLogLevel(Context context, boolean commitToFile) { + Logger.setDefaultLogTag(TermuxConstants.TERMUX_API_APP_NAME.replaceAll(":", "")); + // Load the log level from shared preferences and set it to the {@link Logger.CURRENT_LOG_LEVEL} TermuxAPIAppSharedPreferences preferences = TermuxAPIAppSharedPreferences.build(context); if (preferences == null) return; From 82515f2cab6767b3427d8b552309468b2e3b1533 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 12 Mar 2022 18:53:58 +0500 Subject: [PATCH 017/142] Changed: Move API classes to api package --- app/src/main/AndroidManifest.xml | 121 +- .../java/com/termux/api/DialogActivity.java | 1049 ---------------- .../main/java/com/termux/api/NfcActivity.java | 301 ----- .../com/termux/api/NotificationService.java | 21 - .../com/termux/api/SchedulerJobService.java | 50 - .../com/termux/api/TermuxApiReceiver.java | 45 +- .../TermuxApiPermissionActivity.java | 6 +- .../com/termux/api/{ => apis}/AudioAPI.java | 5 +- .../api/{ => apis}/BatteryStatusAPI.java | 3 +- .../termux/api/{ => apis}/BrightnessAPI.java | 3 +- .../com/termux/api/{ => apis}/CallLogAPI.java | 4 +- .../termux/api/{ => apis}/CameraInfoAPI.java | 5 +- .../termux/api/{ => apis}/ClipboardAPI.java | 5 +- .../termux/api/{ => apis}/ContactListAPI.java | 5 +- .../java/com/termux/api/apis/DialogAPI.java | 1062 +++++++++++++++++ .../termux/api/{ => apis}/DownloadAPI.java | 5 +- .../termux/api/{ => apis}/FingerprintAPI.java | 4 +- .../termux/api/{ => apis}/InfraredAPI.java | 7 +- .../api/{ => apis}/JobSchedulerAPI.java | 51 +- .../termux/api/{ => apis}/KeystoreAPI.java | 7 +- .../termux/api/{ => apis}/LocationAPI.java | 5 +- .../termux/api/{ => apis}/MediaPlayerAPI.java | 4 +- .../api/{ => apis}/MediaScannerAPI.java | 5 +- .../termux/api/{ => apis}/MicRecorderAPI.java | 4 +- .../main/java/com/termux/api/apis/NfcAPI.java | 303 +++++ .../api/{ => apis}/NotificationAPI.java | 10 +- .../api/{ => apis}/NotificationListAPI.java | 25 +- .../com/termux/api/{ => apis}/PhotoAPI.java | 5 +- .../com/termux/api/{ => apis}/SAFAPI.java | 7 +- .../com/termux/api/{ => apis}/SensorAPI.java | 2 +- .../com/termux/api/{ => apis}/ShareAPI.java | 6 +- .../termux/api/{ => apis}/SmsInboxAPI.java | 5 +- .../com/termux/api/{ => apis}/SmsSendAPI.java | 5 +- .../api/{ => apis}/SpeechToTextAPI.java | 2 +- .../termux/api/{ => apis}/StorageGetAPI.java | 5 +- .../termux/api/{ => apis}/TelephonyAPI.java | 9 +- .../api/{ => apis}/TextToSpeechAPI.java | 2 +- .../com/termux/api/{ => apis}/ToastAPI.java | 2 +- .../com/termux/api/{ => apis}/TorchAPI.java | 3 +- .../com/termux/api/{ => apis}/UsbAPI.java | 5 +- .../com/termux/api/{ => apis}/VibrateAPI.java | 5 +- .../com/termux/api/{ => apis}/VolumeAPI.java | 5 +- .../termux/api/{ => apis}/WallpaperAPI.java | 4 +- .../com/termux/api/{ => apis}/WifiAPI.java | 9 +- 44 files changed, 1657 insertions(+), 1539 deletions(-) delete mode 100644 app/src/main/java/com/termux/api/DialogActivity.java delete mode 100644 app/src/main/java/com/termux/api/NfcActivity.java delete mode 100644 app/src/main/java/com/termux/api/NotificationService.java delete mode 100644 app/src/main/java/com/termux/api/SchedulerJobService.java rename app/src/main/java/com/termux/api/{util => activities}/TermuxApiPermissionActivity.java (96%) rename app/src/main/java/com/termux/api/{ => apis}/AudioAPI.java (95%) rename app/src/main/java/com/termux/api/{ => apis}/BatteryStatusAPI.java (98%) rename app/src/main/java/com/termux/api/{ => apis}/BrightnessAPI.java (93%) rename app/src/main/java/com/termux/api/{ => apis}/CallLogAPI.java (97%) rename app/src/main/java/com/termux/api/{ => apis}/CameraInfoAPI.java (97%) rename app/src/main/java/com/termux/api/{ => apis}/ClipboardAPI.java (94%) rename app/src/main/java/com/termux/api/{ => apis}/ContactListAPI.java (93%) create mode 100644 app/src/main/java/com/termux/api/apis/DialogAPI.java rename app/src/main/java/com/termux/api/{ => apis}/DownloadAPI.java (87%) rename app/src/main/java/com/termux/api/{ => apis}/FingerprintAPI.java (98%) rename app/src/main/java/com/termux/api/{ => apis}/InfraredAPI.java (89%) rename app/src/main/java/com/termux/api/{ => apis}/JobSchedulerAPI.java (79%) rename app/src/main/java/com/termux/api/{ => apis}/KeystoreAPI.java (98%) rename app/src/main/java/com/termux/api/{ => apis}/LocationAPI.java (97%) rename app/src/main/java/com/termux/api/{ => apis}/MediaPlayerAPI.java (98%) rename app/src/main/java/com/termux/api/{ => apis}/MediaScannerAPI.java (94%) rename app/src/main/java/com/termux/api/{ => apis}/MicRecorderAPI.java (99%) create mode 100644 app/src/main/java/com/termux/api/apis/NfcAPI.java rename app/src/main/java/com/termux/api/{ => apis}/NotificationAPI.java (97%) rename app/src/main/java/com/termux/api/{ => apis}/NotificationListAPI.java (86%) rename app/src/main/java/com/termux/api/{ => apis}/PhotoAPI.java (98%) rename app/src/main/java/com/termux/api/{ => apis}/SAFAPI.java (98%) rename app/src/main/java/com/termux/api/{ => apis}/SensorAPI.java (99%) rename app/src/main/java/com/termux/api/{ => apis}/ShareAPI.java (97%) rename app/src/main/java/com/termux/api/{ => apis}/SmsInboxAPI.java (97%) rename app/src/main/java/com/termux/api/{ => apis}/SmsSendAPI.java (93%) rename app/src/main/java/com/termux/api/{ => apis}/SpeechToTextAPI.java (99%) rename app/src/main/java/com/termux/api/{ => apis}/StorageGetAPI.java (94%) rename app/src/main/java/com/termux/api/{ => apis}/TelephonyAPI.java (97%) rename app/src/main/java/com/termux/api/{ => apis}/TextToSpeechAPI.java (99%) rename app/src/main/java/com/termux/api/{ => apis}/ToastAPI.java (98%) rename app/src/main/java/com/termux/api/{ => apis}/TorchAPI.java (97%) rename app/src/main/java/com/termux/api/{ => apis}/UsbAPI.java (97%) rename app/src/main/java/com/termux/api/{ => apis}/VibrateAPI.java (82%) rename app/src/main/java/com/termux/api/{ => apis}/VolumeAPI.java (96%) rename app/src/main/java/com/termux/api/{ => apis}/WallpaperAPI.java (98%) rename app/src/main/java/com/termux/api/{ => apis}/WifiAPI.java (93%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c2812d26b..241030820 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,9 +31,9 @@ - - - + + + @@ -59,9 +59,29 @@ android:supportsRtl="true" android:theme="@android:style/Theme.Material.Light" tools:ignore="GoogleAppIndexingWarning"> - - - + + + + + + + + @@ -70,36 +90,52 @@ - - - - - - - - + + + + + + - - - + android:exported="true" /> + + + + + + + + + + + + + + + @@ -108,14 +144,19 @@ - - + + + + + + + diff --git a/app/src/main/java/com/termux/api/DialogActivity.java b/app/src/main/java/com/termux/api/DialogActivity.java deleted file mode 100644 index 25f5ddd56..000000000 --- a/app/src/main/java/com/termux/api/DialogActivity.java +++ /dev/null @@ -1,1049 +0,0 @@ -package com.termux.api; - -import android.Manifest; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.os.Build; -import android.os.Bundle; -import android.speech.RecognitionListener; -import android.speech.RecognizerIntent; -import android.speech.SpeechRecognizer; -import android.util.Log; -import androidx.annotation.NonNull; -import com.google.android.material.bottomsheet.BottomSheetDialog; -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import androidx.core.widget.NestedScrollView; -import androidx.appcompat.app.AppCompatActivity; -import android.text.InputType; -import android.util.JsonWriter; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.DatePicker; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.ScrollView; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.TimePicker; -import android.widget.Toast; - -import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiPermissionActivity; - -import java.nio.charset.StandardCharsets; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.Properties; - -import static com.termux.shared.termux.TermuxConstants.TERMUX_PROPERTIES_PRIMARY_FILE_PATH; -import static com.termux.shared.termux.TermuxConstants.TERMUX_PROPERTIES_SECONDARY_FILE_PATH; - -/** - * API that allows receiving user input interactively in a variety of different ways - */ -public class DialogActivity extends AppCompatActivity { - - private boolean resultReturned = false; - - protected boolean getBlackUI() { - File propsFile = new File(TERMUX_PROPERTIES_PRIMARY_FILE_PATH); - - if (!propsFile.exists()) - propsFile = new File(TERMUX_PROPERTIES_SECONDARY_FILE_PATH); - - boolean mUseBlackUi = false; - - if (propsFile.exists()) { - Properties props = new Properties(); - try { - if (propsFile.isFile() && propsFile.canRead()) { - try (FileInputStream in = new FileInputStream(propsFile)) { - props.load(new InputStreamReader(in, StandardCharsets.UTF_8)); - } - } - mUseBlackUi = props.getProperty("use-black-ui").equals("true"); - } catch (Exception e) { - Log.e("termux-api", "Error loading props", e); - } - } - - return mUseBlackUi; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Intent intent = getIntent(); - final Context context = this; - - - String methodType = intent.hasExtra("input_method") ? intent.getStringExtra("input_method") : ""; - - if (getBlackUI()) - this.setTheme(R.style.DialogTheme_Dark); - - InputMethod method = InputMethodFactory.get(methodType, this); - method.create(this, result -> { - postResult(context, result); - finish(); - }); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - if (!resultReturned) { - postResult(this, null); - } - } - - /** - * Extract value extras from intent into String array - */ - static String[] getInputValues(Intent intent) { - String[] items = new String[] { }; - - if (intent != null && intent.hasExtra("input_values")) { - String[] temp = intent.getStringExtra("input_values").split("(? -1) { - out.name("index").value(result.index); - } - if (result.values.size() > 0) { - out.name("values"); - out.beginArray(); - for (Value value : result.values) { - out.beginObject(); - out.name("index").value(value.index); - out.name("text").value(value.text); - out.endObject(); - } - out.endArray(); - } - if (!result.error.equals("")) { - out.name("error").value(result.error); - } - - out.endObject(); - out.flush(); - resultReturned = true; - } - }); - } - - - /** - * Factory for returning proper input method type that we received in our incoming intent - */ - static class InputMethodFactory { - - public static InputMethod get(final String type, final AppCompatActivity activity) { - - switch (type == null ? "" : type) { - case "confirm": - return new ConfirmInputMethod(activity); - case "checkbox": - return new CheckBoxInputMethod(activity); - case "counter": - return new CounterInputMethod(activity); - case "date": - return new DateInputMethod(activity); - case "radio": - return new RadioInputMethod(activity); - case "sheet": - return new BottomSheetInputMethod(); - case "speech": - return new SpeechInputMethod(activity); - case "spinner": - return new SpinnerInputMethod(activity); - case "text": - return new TextInputMethod(activity); - case "time": - return new TimeInputMethod(activity); - default: - return (activity1, resultListener) -> { - InputResult result = new InputResult(); - result.error = "Unknown Input Method: " + type; - resultListener.onResult(result); - }; - } - } - } - - - /** - * Interface for creating an input method type - */ - interface InputMethod { - void create(AppCompatActivity activity, InputResultListener resultListener); - } - - - /** - * Callback interface for receiving an InputResult - */ - interface InputResultListener { - void onResult(InputResult result); - } - - - /** - * Simple POJO to store the result of input methods - */ - static class InputResult { - public String text = ""; - public String error = ""; - public int code = 0; - public static int index = -1; - public List values = new ArrayList<>(); - } - - - public static class Value { - public int index = -1; - public String text = ""; - } - - /* - * -------------------------------------- - * InputMethod Implementations - * -------------------------------------- - */ - - - /** - * CheckBox InputMethod - * Allow users to select multiple options from a range of values - */ - static class CheckBoxInputMethod extends InputDialog { - - CheckBoxInputMethod(AppCompatActivity activity) { - super(activity); - } - - @Override - LinearLayout createWidgetView(AppCompatActivity activity) { - LinearLayout layout = new LinearLayout(activity); - layout.setOrientation(LinearLayout.VERTICAL); - - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); - layoutParams.topMargin = 32; - layoutParams.bottomMargin = 32; - - String[] values = getInputValues(activity.getIntent()); - - for (int j = 0; j < values.length; ++j) { - String value = values[j]; - - CheckBox checkBox = new CheckBox(activity); - checkBox.setText(value); - checkBox.setId(j); - checkBox.setTextSize(18); - checkBox.setPadding(16, 16, 16, 16); - checkBox.setLayoutParams(layoutParams); - - layout.addView(checkBox); - } - return layout; - } - - @Override - String getResult() { - int checkBoxCount = widgetView.getChildCount(); - - List values = new ArrayList<>(); - StringBuilder sb = new StringBuilder(); - sb.append("["); - - for (int j = 0; j < checkBoxCount; ++j) { - CheckBox box = widgetView.findViewById(j); - if (box.isChecked()) { - Value value = new Value(); - value.index = j; - value.text = box.getText().toString(); - values.add(value); - sb.append(box.getText().toString()).append(", "); - } - } - inputResult.values = values; - // remove trailing comma and add closing bracket - return sb.toString().replaceAll(", $", "") + "]"; - } - } - - - /** - * Confirm InputMethod - * Allow users to confirm YES or NO. - */ - static class ConfirmInputMethod extends InputDialog { - - ConfirmInputMethod(AppCompatActivity activity) { - super(activity); - } - - @Override - InputResult onDialogClick(int button) { - inputResult.text = button == Dialog.BUTTON_POSITIVE ? "yes" : "no"; - return inputResult; - } - - @Override - TextView createWidgetView(AppCompatActivity activity) { - TextView textView = new TextView(activity); - final Intent intent = activity.getIntent(); - - String text = intent.hasExtra("input_hint") ? intent.getStringExtra("input_hint") : "Confirm"; - textView.setText(text); - return textView; - } - - @Override - String getNegativeButtonText() { - return "No"; - } - - @Override - String getPositiveButtonText() { - return "Yes"; - } - } - - - /** - * Counter InputMethod - * Allow users to increment or decrement a number in a given range - */ - static class CounterInputMethod extends InputDialog { - static final int DEFAULT_MIN = 0; - static final int DEFAULT_MAX = 100; - static final int RANGE_LENGTH = 3; - - int min; - int max; - int counter; - - TextView counterLabel; - - CounterInputMethod(AppCompatActivity activity) { - super(activity); - } - - @Override - View createWidgetView(AppCompatActivity activity) { - View layout = View.inflate(activity, R.layout.dialog_counter, null); - counterLabel = layout.findViewById(R.id.counterTextView); - - final Button incrementButton = layout.findViewById(R.id.incrementButton); - incrementButton.setOnClickListener(view -> increment()); - - final Button decrementButton = layout.findViewById(R.id.decrementButton); - decrementButton.setOnClickListener(view -> decrement()); - updateCounterRange(); - - return layout; - } - - void updateCounterRange() { - final Intent intent = activity.getIntent(); - - if (intent.hasExtra("input_range")) { - int[] values = intent.getIntArrayExtra("input_range"); - if (values.length != RANGE_LENGTH) { - inputResult.error = "Invalid range! Must be 3 int values!"; - postCanceledResult(); - dialog.dismiss(); - } else { - min = Math.min(values[0], values[1]); - max = Math.max(values[0], values[1]); - counter = values[2]; - } - } else { - min = DEFAULT_MIN; - max = DEFAULT_MAX; - - // halfway - counter = (DEFAULT_MAX - DEFAULT_MIN) / 2; - } - updateLabel(); - } - - @Override - String getResult() { - return counterLabel.getText().toString(); - } - - void updateLabel() { - counterLabel.setText(String.valueOf(counter)); - } - - void increment() { - if ((counter + 1) <= max) { - ++counter; - updateLabel(); - } - } - - void decrement() { - if ((counter - 1) >= min) { - --counter; - updateLabel(); - } - } - } - - - /** - * Date InputMethod - * Allow users to pick a specific date - */ - static class DateInputMethod extends InputDialog { - - DateInputMethod(AppCompatActivity activity) { - super(activity); - } - - @Override - String getResult() { - int month = widgetView.getMonth(); - int day = widgetView.getDayOfMonth(); - int year = widgetView.getYear(); - - Calendar calendar = Calendar.getInstance(); - calendar.set(year, month, day, 0, 0, 0); - - final Intent intent = activity.getIntent(); - if (intent.hasExtra("date_format")) { - String date_format = intent.getStringExtra("date_format"); - try { - SimpleDateFormat dateFormat = new SimpleDateFormat(date_format); - dateFormat.setTimeZone(calendar.getTimeZone()); - return dateFormat.format(calendar.getTime()); - } catch (Exception e) { - inputResult.error = e.toString(); - postCanceledResult(); - } - } - return calendar.getTime().toString(); - } - - @Override - DatePicker createWidgetView(AppCompatActivity activity) { - return new DatePicker(activity); - } - } - - - /** - * Text InputMethod - * Allow users to enter plaintext or a password - */ - static class TextInputMethod extends InputDialog { - - TextInputMethod(AppCompatActivity activity) { - super(activity); - } - - @Override - String getResult() { - return widgetView.getText().toString(); - } - - @Override - EditText createWidgetView(AppCompatActivity activity) { - final Intent intent = activity.getIntent(); - EditText editText = new EditText(activity); - - if (intent.hasExtra("input_hint")) { - editText.setHint(intent.getStringExtra("input_hint")); - } - - boolean multiLine = intent.getBooleanExtra("multiple_lines", false); - boolean numeric = intent.getBooleanExtra("numeric", false); - boolean password = intent.getBooleanExtra("password", false); - - int flags = InputType.TYPE_CLASS_TEXT; - - if (password) { - flags = numeric ? (flags | InputType.TYPE_NUMBER_VARIATION_PASSWORD) : (flags | InputType.TYPE_TEXT_VARIATION_PASSWORD); - } - - if (multiLine) { - flags |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; - editText.setLines(4); - } - - if (numeric) { - flags &= ~InputType.TYPE_CLASS_TEXT; // clear to allow only numbers - flags |= InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL; - } - - editText.setInputType(flags); - - return editText; - } - } - - - /** - * Time InputMethod - * Allow users to pick a specific time - */ - static class TimeInputMethod extends InputDialog { - - TimeInputMethod(AppCompatActivity activity) { - super(activity); - } - - @Override - String getResult() { - String result = String.format(Locale.getDefault(), "%02d:%02d", widgetView.getHour(), widgetView.getMinute()); - return result; - } - - @Override - TimePicker createWidgetView(AppCompatActivity activity) { - return new TimePicker(activity); - } - } - - - /** - * Radio InputMethod - * Allow users to confirm from radio button options - */ - static class RadioInputMethod extends InputDialog { - RadioGroup radioGroup; - - RadioInputMethod(AppCompatActivity activity) { - super(activity); - } - - @Override - RadioGroup createWidgetView(AppCompatActivity activity) { - radioGroup = new RadioGroup(activity); - radioGroup.setPadding(16, 16, 16, 16); - - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); - layoutParams.topMargin = 32; - layoutParams.bottomMargin = 32; - - String[] values = getInputValues(activity.getIntent()); - - for (int j = 0; j < values.length; ++j) { - String value = values[j]; - - RadioButton button = new RadioButton(activity); - button.setText(value); - button.setId(j); - button.setTextSize(18); - button.setPadding(16, 16, 16, 16); - button.setLayoutParams(layoutParams); - - radioGroup.addView(button); - } - return radioGroup; - } - - @Override - String getResult() { - int radioIndex = radioGroup.indexOfChild(widgetView.findViewById(radioGroup.getCheckedRadioButtonId())); - RadioButton radioButton = (RadioButton) radioGroup.getChildAt(radioIndex); - InputResult.index = radioIndex; - return (radioButton != null) ? radioButton.getText().toString() : ""; - } - } - - - /** - * BottomSheet InputMethod - * Allow users to select from a variety of options in a bottom sheet dialog - */ - public static class BottomSheetInputMethod extends BottomSheetDialogFragment implements InputMethod { - private InputResultListener resultListener; - - - @Override - public void create(AppCompatActivity activity, InputResultListener resultListener) { - this.resultListener = resultListener; - show(activity.getSupportFragmentManager(), "BOTTOM_SHEET"); - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - // create custom BottomSheetDialog that has friendlier dismissal behavior - return new BottomSheetDialog(getActivity(), getTheme()) { - @Override - public void onBackPressed() { - super.onBackPressed(); - // make it so that user only has to hit back key one time to get rid of bottom sheet - getActivity().onBackPressed(); - postCanceledResult(); - } - - @Override - public void cancel() { - super.cancel(); - - if (isCurrentAppTermux()) { - showKeyboard(); - } - // dismiss on single touch outside of dialog - getActivity().onBackPressed(); - postCanceledResult(); - } - }; - } - - @Override - public void setupDialog(final Dialog dialog, int style) { - LinearLayout layout = new LinearLayout(getContext()); - layout.setMinimumHeight(100); - layout.setPadding(16, 16, 16, 16); - layout.setOrientation(LinearLayout.VERTICAL); - - NestedScrollView scrollView = new NestedScrollView(getContext()); - final String[] values = getInputValues(Objects.requireNonNull(getActivity()).getIntent()); - - for (int i = 0; i < values.length; ++i) { - final int j = i; - final TextView textView = new TextView(getContext()); - textView.setText(values[j]); - textView.setTextSize(20); - textView.setPadding(56, 56, 56, 56); - textView.setOnClickListener(view -> { - InputResult result = new InputResult(); - result.text = values[j]; - result.index = j; - dialog.dismiss(); - resultListener.onResult(result); - }); - - layout.addView(textView); - } - scrollView.addView(layout); - dialog.setContentView(scrollView); - hideKeyboard(); - } - - /** - * These keyboard methods exist to work around inconsistent show / hide behavior - * from canceling BottomSheetDialog and produces the desired result of hiding keyboard - * on creation of dialog and showing it after a selection or cancellation, as long as - * we are still within the Termux application - */ - - protected void hideKeyboard() { - getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); - } - - protected void showKeyboard() { - getInputMethodManager().showSoftInput(getView(), InputMethodManager.SHOW_FORCED); - } - - protected InputMethodManager getInputMethodManager() { - return (InputMethodManager) Objects.requireNonNull(getContext()).getSystemService(Context.INPUT_METHOD_SERVICE); - } - - /** - * Checks to see if foreground application is Termux - */ - protected boolean isCurrentAppTermux() { - final ActivityManager activityManager = (ActivityManager) Objects.requireNonNull(getContext()).getSystemService(Context.ACTIVITY_SERVICE); - final List runningProcesses = Objects.requireNonNull(activityManager).getRunningAppProcesses(); - for (final ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { - if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { - for (final String activeProcess : processInfo.pkgList) { - if (activeProcess.equals("com.termux")) { - return true; - } - } - } - } - return false; - } - - protected void postCanceledResult() { - InputResult result = new InputResult(); - result.code = Dialog.BUTTON_NEGATIVE; - resultListener.onResult(result); - } - } - - - /** - * Spinner InputMethod - * Allow users to make a selection based on a list of specified values - */ - static class SpinnerInputMethod extends InputDialog { - - SpinnerInputMethod(AppCompatActivity activity) { - super(activity); - } - - @Override - String getResult() { - InputResult.index = widgetView.getSelectedItemPosition(); - return widgetView.getSelectedItem().toString(); - } - - @Override - Spinner createWidgetView(AppCompatActivity activity) { - Spinner spinner = new Spinner(activity); - - final Intent intent = activity.getIntent(); - final String[] items = getInputValues(intent); - final ArrayAdapter adapter = new ArrayAdapter<>(activity, R.layout.spinner_item, items); - - spinner.setAdapter(adapter); - return spinner; - } - } - - - /** - * Speech InputMethod - * Allow users to use the built in microphone to get text from speech - */ - static class SpeechInputMethod extends InputDialog { - - SpeechInputMethod(AppCompatActivity activity) { - super(activity); - } - - @Override - TextView createWidgetView(AppCompatActivity activity) { - TextView textView = new TextView(activity); - final Intent intent = activity.getIntent(); - - String text = intent.hasExtra("input_hint") ? intent.getStringExtra("input_hint") : "Listening for speech..."; - - textView.setText(text); - textView.setTextSize(20); - return textView; - } - - @Override - public void create(final AppCompatActivity activity, final InputResultListener resultListener) { - // Since we're using the microphone, we need to make sure we have proper permission - if (!TermuxApiPermissionActivity.checkAndRequestPermissions(activity, activity.getIntent(), Manifest.permission.RECORD_AUDIO)) { - activity.finish(); - } - - if (!hasSpeechRecognizer(activity)) { - Toast.makeText(activity, "No voice recognition found!", Toast.LENGTH_SHORT).show(); - activity.finish(); - } - - - Intent speechIntent = createSpeechIntent(); - final SpeechRecognizer recognizer = createSpeechRecognizer(activity, resultListener); - - // create intermediate InputResultListener so that we can stop our speech listening - // if user hits the cancel button - DialogInterface.OnClickListener clickListener = getClickListener(result -> { - recognizer.stopListening(); - resultListener.onResult(result); - }); - - Dialog dialog = getDialogBuilder(activity, clickListener) - .setPositiveButton(null, null) - .setOnDismissListener(null) - .create(); - - dialog.setCanceledOnTouchOutside(false); - dialog.show(); - - recognizer.startListening(speechIntent); - } - - private boolean hasSpeechRecognizer(Context context) { - List installList = context.getPackageManager().queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0); - return !installList.isEmpty(); - } - - private Intent createSpeechIntent() { - Intent speechIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); - speechIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1); - speechIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); - return speechIntent; - } - - private SpeechRecognizer createSpeechRecognizer(AppCompatActivity activity, final InputResultListener listener) { - SpeechRecognizer recognizer = SpeechRecognizer.createSpeechRecognizer(activity); - recognizer.setRecognitionListener(new RecognitionListener() { - - @Override - public void onResults(Bundle results) { - List voiceResults = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); - - if (voiceResults != null && voiceResults.size() > 0) { - inputResult.text = voiceResults.get(0); - } - listener.onResult(inputResult); - } - - /** - * Get string description for error code - */ - @Override - public void onError(int error) { - String errorDescription; - - switch (error) { - case SpeechRecognizer.ERROR_AUDIO: - errorDescription = "ERROR_AUDIO"; - break; - case SpeechRecognizer.ERROR_CLIENT: - errorDescription = "ERROR_CLIENT"; - break; - case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS: - errorDescription = "ERROR_INSUFFICIENT_PERMISSIONS"; - break; - case SpeechRecognizer.ERROR_NETWORK: - errorDescription = "ERROR_NETWORK"; - break; - case SpeechRecognizer.ERROR_NETWORK_TIMEOUT: - errorDescription = "ERROR_NETWORK_TIMEOUT"; - break; - case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: - errorDescription = "ERROR_SPEECH_TIMEOUT"; - break; - default: - errorDescription = "ERROR_UNKNOWN"; - break; - } - inputResult.error = errorDescription; - listener.onResult(inputResult); - } - - - // unused - @Override - public void onEndOfSpeech() { } - - @Override - public void onReadyForSpeech(Bundle bundle) { } - - @Override - public void onBeginningOfSpeech() { } - - @Override - public void onRmsChanged(float v) { } - - @Override - public void onBufferReceived(byte[] bytes) { } - - @Override - public void onPartialResults(Bundle bundle) { } - - @Override - public void onEvent(int i, Bundle bundle) { } - }); - return recognizer; - } - } - - - /** - * Base Dialog class to extend from for adding specific views / widgets to a Dialog interface - * @param Main view type that will be displayed within dialog - */ - abstract static class InputDialog implements InputMethod { - // result that belongs to us - InputResult inputResult = new InputResult(); - - // listener for our input result - InputResultListener resultListener; - - // view that will be placed in our dialog - T widgetView; - - // dialog that holds everything - Dialog dialog; - - // our activity context - AppCompatActivity activity; - - - // method to be implemented that handles creating view that is placed in our dialog - abstract T createWidgetView(AppCompatActivity activity); - - // method that should be implemented that handles returning a result obtained through user input - String getResult() { - return null; - } - - - InputDialog(AppCompatActivity activity) { - this.activity = activity; - widgetView = createWidgetView(activity); - initActivityDisplay(activity); - } - - - @Override - public void create(AppCompatActivity activity, final InputResultListener resultListener) { - this.resultListener = resultListener; - - // Handle OK and Cancel button clicks - DialogInterface.OnClickListener clickListener = getClickListener(resultListener); - - // Dialog interface that will display to user - dialog = getDialogBuilder(activity, clickListener).create(); - dialog.show(); - } - - void postCanceledResult() { - inputResult.code = Dialog.BUTTON_NEGATIVE; - resultListener.onResult(inputResult); - } - - void initActivityDisplay(Activity activity) { - activity.setFinishOnTouchOutside(false); - activity.requestWindowFeature(Window.FEATURE_NO_TITLE); - } - - /** - * Places our generic widget view type inside a FrameLayout - */ - View getLayoutView(AppCompatActivity activity, T view) { - FrameLayout layout = getFrameLayout(activity); - ViewGroup.LayoutParams params = layout.getLayoutParams(); - - view.setLayoutParams(params); - layout.addView(view); - layout.setScrollbarFadingEnabled(false); - - // wrap everything in scrollview - ScrollView scrollView = new ScrollView(activity); - scrollView.addView(layout); - - return scrollView; - } - - DialogInterface.OnClickListener getClickListener(final InputResultListener listener) { - return (dialogInterface, button) -> { - InputResult result = onDialogClick(button); - listener.onResult(result); - }; - } - - DialogInterface.OnDismissListener getDismissListener() { - return dialogInterface -> { - // force dismiss behavior on single tap outside of dialog - activity.onBackPressed(); - onDismissed(); - }; - } - - /** - * Creates a dialog builder to initialize a dialog w/ a view and button click listeners - */ - AlertDialog.Builder getDialogBuilder(AppCompatActivity activity, DialogInterface.OnClickListener clickListener) { - final Intent intent = activity.getIntent(); - final View layoutView = getLayoutView(activity, widgetView); - - return new AlertDialog.Builder(activity) - .setTitle(intent.hasExtra("input_title") ? intent.getStringExtra("input_title") : "") - .setNegativeButton(getNegativeButtonText(), clickListener) - .setPositiveButton(getPositiveButtonText(), clickListener) - .setOnDismissListener(getDismissListener()) - .setView(layoutView); - - } - - String getNegativeButtonText() { - return "Cancel"; - } - - String getPositiveButtonText() { - return "OK"; - } - - void onDismissed() { - postCanceledResult(); - } - - /** - * Create a basic frame layout that will add a margin around our main widget view - */ - FrameLayout getFrameLayout(AppCompatActivity activity) { - FrameLayout layout = new FrameLayout(activity); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - final int margin = 56; - params.setMargins(margin, margin, margin, margin); - - params.setMargins(56, 56, 56, 56); - layout.setLayoutParams(params); - return layout; - } - - /** - * Returns an InputResult containing code of our button and the text if we hit OK - */ - InputResult onDialogClick(int button) { - // receive indication of whether the OK or CANCEL button is clicked - inputResult.code = button; - - // OK clicked - if (button == Dialog.BUTTON_POSITIVE) { - inputResult.text = getResult(); - } - return inputResult; - } - } -} diff --git a/app/src/main/java/com/termux/api/NfcActivity.java b/app/src/main/java/com/termux/api/NfcActivity.java deleted file mode 100644 index f305b8e1c..000000000 --- a/app/src/main/java/com/termux/api/NfcActivity.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.termux.api; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.PendingIntent; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; -import android.nfc.NfcAdapter; -import android.nfc.Tag; -import android.nfc.tech.Ndef; -import android.os.Bundle; -import android.os.Parcelable; -import android.text.Layout; -import android.util.JsonWriter; -import android.util.Log; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; - -import com.termux.api.util.ResultReturner; - - -public class NfcActivity extends AppCompatActivity{ - private NfcAdapter adapter; - static String socket_input; - static String socket_output; - String mode; - String param; - String value; - //Check for NFC - protected void errorNfc(final Context context, Intent intent, String error) { - ResultReturner.returnData(context, intent, new ResultReturner.ResultJsonWriter() { - @Override - public void writeJson(JsonWriter out) throws Exception { - NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); - out.beginObject(); - if (error.length() > 0) - out.name("error").value(error); - out.name("nfcPresent").value(null != adapter); - if(null!=adapter) - out.name("nfcActive").value(adapter.isEnabled()); - out.endObject(); - } - }); - } - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Intent intent = this.getIntent(); - if (intent != null) { - mode = intent.getStringExtra("mode"); - if (null == mode) - mode = "noData"; - param =intent.getStringExtra("param"); - if (null == param) - param = "noData"; - value=intent.getStringExtra("value"); - if (null == socket_input) socket_input = intent.getStringExtra("socket_input"); - if (null == socket_output) socket_output = intent.getStringExtra("socket_output"); - if (mode == "noData") { - errorNfc(this, intent,""); - finish(); - } - } - - NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); - if((null==adapter)||(!adapter.isEnabled())){ - errorNfc(this,intent,""); - finish(); - } - } - - @Override - protected void onResume() { - super.onResume(); - adapter = NfcAdapter.getDefaultAdapter(this); - Intent intentNew = new Intent(this, NfcActivity.class).addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intentNew, 0); - IntentFilter[] intentFilter = new IntentFilter[]{ - new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED), - new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED), - new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)}; - adapter.enableForegroundDispatch(this, pendingIntent, intentFilter, null); - } - - @Override - protected void onNewIntent(Intent intent) { - intent.putExtra("socket_input", socket_input); - intent.putExtra("socket_output", socket_output); - - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { - try { - postResult(this, intent); - } - catch (Exception e) - { - Log.e("Termix-api.NfcAction",e.getMessage()); - } - finish(); - } - super.onNewIntent(intent); - } - - @Override - protected void onPause() { - adapter.disableForegroundDispatch(this); - super.onPause(); - } - - @Override - protected void onDestroy() { - socket_input = null; - socket_output = null; - super.onDestroy(); - } - - protected void postResult(final Context context, Intent intent) { - ResultReturner.returnData(context, intent, new ResultReturner.ResultJsonWriter() { - @Override - public void writeJson(JsonWriter out) throws Exception { - Log.e("NFC","postResult"); - try - { - switch (mode) { - case "write": - switch (param) { - case "text": - Log.e("NFC","-->write"); - onReceiveNfcWrite(context, intent); - Log.e("NFC","<--write"); - break; - default: - onUnexpectedAction(out, "Wrong Params", "Should be text for TAG"); - break; - } - break; - case "read": - switch (param){ - case "short": - readNDEFTag(intent,out); - break; - case "full": - readFullNDEFTag(intent,out); - break; - case "noData": - readNDEFTag(intent,out); - break; - default: - onUnexpectedAction(out, "Wrong Params", "Should be correct param value"); - break; - } - break; - default: - onUnexpectedAction(out, "Wrong Params", "Should be correct mode value "); - break; - } - } - catch (Exception e){ - onUnexpectedAction(out, "exception", e.getMessage()); - } - } - }); - } - public void onReceiveNfcWrite( final Context context, Intent intent) throws Exception { - { - Log.e("NFC","---->onReceiveNfcWrite"); - NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); - Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - NdefRecord record = NdefRecord.createTextRecord("en", value); - NdefMessage msg = new NdefMessage(new NdefRecord[]{record}); - Ndef ndef = Ndef.get(tag); - ndef.connect(); - ndef.writeNdefMessage(msg); - ndef.close(); - } - } - - - public void readNDEFTag(Intent intent, JsonWriter out) throws Exception { - NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); - Parcelable[] msgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); - Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - Ndef ndefTag = Ndef.get(tag); - boolean bNdefPresent = false; - String strs[] = tag.getTechList(); - for (String s: strs){ - if (s.equals("android.nfc.tech.Ndef")) - bNdefPresent = true; - } - if (!bNdefPresent){ - onUnexpectedAction(out, "Wrong Technology","termux API support only NFEF Tag"); - return; - } - NdefMessage[] nmsgs = new NdefMessage[msgs.length]; - if (msgs.length == 1) { - nmsgs[0] = (NdefMessage) msgs[0]; - NdefRecord records[] = nmsgs[0].getRecords(); - out.beginObject(); - if (records.length >0 ) { - { - out.name("Record"); - if (records.length > 1) - out.beginArray(); - for (NdefRecord record: records){ - out.beginObject(); - int pos = 1 + record.getPayload()[0]; - pos = (NdefRecord.TNF_WELL_KNOWN==record.getTnf())?(int)record.getPayload()[0]+1:0; - int len = record.getPayload().length - pos; - byte msg[] = new byte[len]; - System.arraycopy(record.getPayload(), pos, msg, 0, len); - out.name("Payload").value(new String(msg)); - out.endObject(); - } - if (records.length > 1) - out.endArray(); - } - } - out.endObject(); - } - } - - public void readFullNDEFTag(Intent intent, JsonWriter out) throws Exception { - NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); - Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - Ndef ndefTag = Ndef.get(tag); - Parcelable[] msgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); - - String strs[] = tag.getTechList(); - boolean bNdefPresent = false; - for (String s: strs){ - if (s.equals("android.nfc.tech.Ndef")) - bNdefPresent = true; - } - if (!bNdefPresent){ - onUnexpectedAction(out, "Wrong Technology","termux API support only NFEF Tag"); - return; - } - NdefMessage[] nmsgs = new NdefMessage[msgs.length]; - out.beginObject(); - { - byte[] tagID = tag.getId(); - StringBuilder sp = new StringBuilder(); - for (byte tagIDpart : tagID) { sp.append(String.format("%02x", tagIDpart)); } - out.name("id").value(sp.toString()); - out.name("typeTag").value(ndefTag.getType()); - out.name("maxSize").value(ndefTag.getMaxSize()); - out.name("techList"); - { - out.beginArray(); - String[] tlist = tag.getTechList(); - for (String str : tlist) { - out.value(str); - } - out.endArray(); - } - if (msgs.length == 1) { - Log.e("NFC", "-->> readFullNDEFTag - 06"); - nmsgs[0] = (NdefMessage) msgs[0]; - NdefRecord records[] = nmsgs[0].getRecords(); - { - out.name("record"); - if (records.length > 1) - out.beginArray(); - for (NdefRecord record : records) { - out.beginObject(); - out.name("type").value(new String(record.getType())); - out.name("tnf").value(record.getTnf()); - if (records[0].toUri() != null) out.name("URI").value(record.toUri().toString()); - out.name("mime").value(record.toMimeType()); - int pos = 1 + record.getPayload()[0]; - pos = (NdefRecord.TNF_WELL_KNOWN==record.getTnf())?(int)record.getPayload()[0]+1:0; - int len = record.getPayload().length - pos; - byte msg[] = new byte[len]; - System.arraycopy(record.getPayload(), pos, msg, 0, len); - out.name("payload").value(new String(msg)); - out.endObject(); - } - if (records.length > 1) out.endArray(); - } - } - - } - out.endObject(); - } - - protected void onUnexpectedAction(JsonWriter out,String error, String description) throws Exception { - out.beginObject(); - out.name("error").value(error); - out.name("description").value(description); - out.endObject(); - out.flush(); - } -} diff --git a/app/src/main/java/com/termux/api/NotificationService.java b/app/src/main/java/com/termux/api/NotificationService.java deleted file mode 100644 index 541217864..000000000 --- a/app/src/main/java/com/termux/api/NotificationService.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.termux.api; - -import android.service.notification.NotificationListenerService; - -public class NotificationService extends NotificationListenerService { - static NotificationService _this; - - public static NotificationService get() { - return _this; - } - - @Override - public void onListenerConnected() { - _this = this; - } - - @Override - public void onListenerDisconnected() { - _this = null; - } -} diff --git a/app/src/main/java/com/termux/api/SchedulerJobService.java b/app/src/main/java/com/termux/api/SchedulerJobService.java deleted file mode 100644 index 65594a7c6..000000000 --- a/app/src/main/java/com/termux/api/SchedulerJobService.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.termux.api; -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.PersistableBundle; -import android.util.Log; - -public class SchedulerJobService extends JobService { - - private static final String LOG_TAG = "TermuxAPISchedulerJob"; - public static final String SCRIPT_FILE_PATH = "com.termux.api.jobscheduler_script_path"; - - // Constants from TermuxService. - private static final String TERMUX_SERVICE = "com.termux.app.TermuxService"; - private static final String ACTION_EXECUTE = "com.termux.service_execute"; - private static final String EXTRA_EXECUTE_IN_BACKGROUND = "com.termux.execute.background"; - - @Override - public boolean onStartJob(JobParameters params) { - - Log.i(LOG_TAG, "Starting job " + params.toString()); - PersistableBundle extras = params.getExtras(); - String filePath = extras.getString(SCRIPT_FILE_PATH); - - Uri scriptUri = new Uri.Builder().scheme("com.termux.file").path(filePath).build(); - Intent executeIntent = new Intent(ACTION_EXECUTE, scriptUri); - executeIntent.setClassName("com.termux", TERMUX_SERVICE); - executeIntent.putExtra(EXTRA_EXECUTE_IN_BACKGROUND, true); - - Context context = getApplicationContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // https://developer.android.com/about/versions/oreo/background.html - context.startForegroundService(executeIntent); - } else { - context.startService(executeIntent); - } - - Log.i(LOG_TAG, "Started job " + params.toString()); - return false; - } - - @Override - public boolean onStopJob(JobParameters params) { - Log.i(LOG_TAG, "Stopped job " + params.toString()); - return false; - } -} diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index c94dd0696..6f00595b5 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -8,8 +8,45 @@ import android.provider.Settings; import android.widget.Toast; +import com.termux.api.apis.AudioAPI; +import com.termux.api.apis.BatteryStatusAPI; +import com.termux.api.apis.BrightnessAPI; +import com.termux.api.apis.CallLogAPI; +import com.termux.api.apis.CameraInfoAPI; +import com.termux.api.apis.ClipboardAPI; +import com.termux.api.apis.ContactListAPI; +import com.termux.api.apis.DialogAPI; +import com.termux.api.apis.DownloadAPI; +import com.termux.api.apis.FingerprintAPI; +import com.termux.api.apis.InfraredAPI; +import com.termux.api.apis.JobSchedulerAPI; +import com.termux.api.apis.KeystoreAPI; +import com.termux.api.apis.LocationAPI; +import com.termux.api.apis.MediaPlayerAPI; +import com.termux.api.apis.MediaScannerAPI; +import com.termux.api.apis.MicRecorderAPI; +import com.termux.api.apis.NfcAPI; +import com.termux.api.apis.NotificationAPI; +import com.termux.api.apis.NotificationListAPI; +import com.termux.api.apis.PhotoAPI; +import com.termux.api.apis.SAFAPI; +import com.termux.api.apis.SensorAPI; +import com.termux.api.apis.ShareAPI; +import com.termux.api.apis.SmsInboxAPI; +import com.termux.api.apis.SmsSendAPI; +import com.termux.api.apis.SpeechToTextAPI; +import com.termux.api.apis.StorageGetAPI; +import com.termux.api.apis.TelephonyAPI; +import com.termux.api.apis.TextToSpeechAPI; +import com.termux.api.apis.ToastAPI; +import com.termux.api.apis.TorchAPI; +import com.termux.api.apis.UsbAPI; +import com.termux.api.apis.VibrateAPI; +import com.termux.api.apis.VolumeAPI; +import com.termux.api.apis.WallpaperAPI; +import com.termux.api.apis.WifiAPI; import com.termux.api.util.TermuxApiLogger; -import com.termux.api.util.TermuxApiPermissionActivity; +import com.termux.api.activities.TermuxApiPermissionActivity; public class TermuxApiReceiver extends BroadcastReceiver { @@ -72,7 +109,7 @@ private void doWork(Context context, Intent intent) { } break; case "Dialog": - context.startActivity(new Intent(context, DialogActivity.class).putExtras(intent.getExtras()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + DialogAPI.onReceive(context, intent); break; case "Download": DownloadAPI.onReceive(this, context, intent); @@ -113,10 +150,10 @@ private void doWork(Context context, Intent intent) { } break; case "Nfc": - context.startActivity(new Intent(context, NfcActivity.class).putExtras(intent.getExtras()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + NfcAPI.onReceive(context, intent); break; case "NotificationList": - ComponentName cn = new ComponentName(context, NotificationService.class); + ComponentName cn = new ComponentName(context, NotificationListAPI.NotificationService.class); String flat = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners"); final boolean NotificationServiceEnabled = flat != null && flat.contains(cn.flattenToString()); if (!NotificationServiceEnabled) { diff --git a/app/src/main/java/com/termux/api/util/TermuxApiPermissionActivity.java b/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java similarity index 96% rename from app/src/main/java/com/termux/api/util/TermuxApiPermissionActivity.java rename to app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java index 2fcaa611a..ab9fa42e9 100644 --- a/app/src/main/java/com/termux/api/util/TermuxApiPermissionActivity.java +++ b/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java @@ -1,14 +1,14 @@ -package com.termux.api.util; +package com.termux.api.activities; -import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.os.Build; import android.text.TextUtils; import android.util.JsonWriter; +import com.termux.api.util.ResultReturner; + import java.util.ArrayList; public class TermuxApiPermissionActivity extends Activity { diff --git a/app/src/main/java/com/termux/api/AudioAPI.java b/app/src/main/java/com/termux/api/apis/AudioAPI.java similarity index 95% rename from app/src/main/java/com/termux/api/AudioAPI.java rename to app/src/main/java/com/termux/api/apis/AudioAPI.java index ebb4c2dee..756cc67bf 100644 --- a/app/src/main/java/com/termux/api/AudioAPI.java +++ b/app/src/main/java/com/termux/api/apis/AudioAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; @@ -8,11 +8,12 @@ import android.os.Build; import android.util.JsonWriter; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; public class AudioAPI { - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); final String SampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); final String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); diff --git a/app/src/main/java/com/termux/api/BatteryStatusAPI.java b/app/src/main/java/com/termux/api/apis/BatteryStatusAPI.java similarity index 98% rename from app/src/main/java/com/termux/api/BatteryStatusAPI.java rename to app/src/main/java/com/termux/api/apis/BatteryStatusAPI.java index d51143f6a..7d504da3d 100644 --- a/app/src/main/java/com/termux/api/BatteryStatusAPI.java +++ b/app/src/main/java/com/termux/api/apis/BatteryStatusAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; @@ -6,6 +6,7 @@ import android.os.BatteryManager; import android.util.JsonWriter; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; import com.termux.api.util.TermuxApiLogger; diff --git a/app/src/main/java/com/termux/api/BrightnessAPI.java b/app/src/main/java/com/termux/api/apis/BrightnessAPI.java similarity index 93% rename from app/src/main/java/com/termux/api/BrightnessAPI.java rename to app/src/main/java/com/termux/api/apis/BrightnessAPI.java index 198dc962a..492bf3ca8 100644 --- a/app/src/main/java/com/termux/api/BrightnessAPI.java +++ b/app/src/main/java/com/termux/api/apis/BrightnessAPI.java @@ -1,10 +1,11 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.provider.Settings; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; public class BrightnessAPI { diff --git a/app/src/main/java/com/termux/api/CallLogAPI.java b/app/src/main/java/com/termux/api/apis/CallLogAPI.java similarity index 97% rename from app/src/main/java/com/termux/api/CallLogAPI.java rename to app/src/main/java/com/termux/api/apis/CallLogAPI.java index 5e69f27bd..1658799c0 100644 --- a/app/src/main/java/com/termux/api/CallLogAPI.java +++ b/app/src/main/java/com/termux/api/apis/CallLogAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.ContentResolver; import android.content.Context; @@ -20,7 +20,7 @@ */ public class CallLogAPI { - static void onReceive(final Context context, final Intent intent) { + public static void onReceive(final Context context, final Intent intent) { final int offset = intent.getIntExtra("offset", 0); final int limit = intent.getIntExtra("limit", 50); diff --git a/app/src/main/java/com/termux/api/CameraInfoAPI.java b/app/src/main/java/com/termux/api/apis/CameraInfoAPI.java similarity index 97% rename from app/src/main/java/com/termux/api/CameraInfoAPI.java rename to app/src/main/java/com/termux/api/apis/CameraInfoAPI.java index 24ba835a1..38300977e 100644 --- a/app/src/main/java/com/termux/api/CameraInfoAPI.java +++ b/app/src/main/java/com/termux/api/apis/CameraInfoAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; @@ -11,12 +11,13 @@ import android.util.Size; import android.util.SizeF; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; public class CameraInfoAPI { - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { diff --git a/app/src/main/java/com/termux/api/ClipboardAPI.java b/app/src/main/java/com/termux/api/apis/ClipboardAPI.java similarity index 94% rename from app/src/main/java/com/termux/api/ClipboardAPI.java rename to app/src/main/java/com/termux/api/apis/ClipboardAPI.java index e88583e1a..d12be9797 100644 --- a/app/src/main/java/com/termux/api/ClipboardAPI.java +++ b/app/src/main/java/com/termux/api/apis/ClipboardAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.ClipData; import android.content.ClipData.Item; @@ -7,13 +7,14 @@ import android.content.Intent; import android.text.TextUtils; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import java.io.PrintWriter; public class ClipboardAPI { - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); final ClipData clipData = clipboard.getPrimaryClip(); diff --git a/app/src/main/java/com/termux/api/ContactListAPI.java b/app/src/main/java/com/termux/api/apis/ContactListAPI.java similarity index 93% rename from app/src/main/java/com/termux/api/ContactListAPI.java rename to app/src/main/java/com/termux/api/apis/ContactListAPI.java index 12df9393c..9b168d508 100644 --- a/app/src/main/java/com/termux/api/ContactListAPI.java +++ b/app/src/main/java/com/termux/api/apis/ContactListAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.ContentResolver; import android.content.Context; @@ -10,12 +10,13 @@ import android.util.JsonWriter; import android.util.SparseArray; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; public class ContactListAPI { - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java new file mode 100644 index 000000000..3c866b38c --- /dev/null +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -0,0 +1,1062 @@ +package com.termux.api.apis; + +import static com.termux.shared.termux.TermuxConstants.TERMUX_PROPERTIES_PRIMARY_FILE_PATH; +import static com.termux.shared.termux.TermuxConstants.TERMUX_PROPERTIES_SECONDARY_FILE_PATH; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.speech.RecognitionListener; +import android.speech.RecognizerIntent; +import android.speech.SpeechRecognizer; +import android.text.InputType; +import android.util.JsonWriter; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.ScrollView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.TimePicker; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.widget.NestedScrollView; + +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.termux.api.R; +import com.termux.api.util.ResultReturner; +import com.termux.api.activities.TermuxApiPermissionActivity; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Properties; + +/** + * API that allows receiving user input interactively in a variety of different ways + */ +public class DialogAPI { + + public static void onReceive(final Context context, final Intent intent) { + context.startActivity(new Intent(context, DialogActivity.class).putExtras(intent.getExtras()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + + + + public static class DialogActivity extends AppCompatActivity { + + private boolean resultReturned = false; + + protected boolean getBlackUI() { + File propsFile = new File(TERMUX_PROPERTIES_PRIMARY_FILE_PATH); + + if (!propsFile.exists()) + propsFile = new File(TERMUX_PROPERTIES_SECONDARY_FILE_PATH); + + boolean mUseBlackUi = false; + + if (propsFile.exists()) { + Properties props = new Properties(); + try { + if (propsFile.isFile() && propsFile.canRead()) { + try (FileInputStream in = new FileInputStream(propsFile)) { + props.load(new InputStreamReader(in, StandardCharsets.UTF_8)); + } + } + mUseBlackUi = props.getProperty("use-black-ui").equals("true"); + } catch (Exception e) { + Log.e("termux-api", "Error loading props", e); + } + } + + return mUseBlackUi; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Intent intent = getIntent(); + final Context context = this; + + + String methodType = intent.hasExtra("input_method") ? intent.getStringExtra("input_method") : ""; + + if (getBlackUI()) + this.setTheme(R.style.DialogTheme_Dark); + + InputMethod method = InputMethodFactory.get(methodType, this); + method.create(this, result -> { + postResult(context, result); + finish(); + }); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (!resultReturned) { + postResult(this, null); + } + } + + /** + * Extract value extras from intent into String array + */ + static String[] getInputValues(Intent intent) { + String[] items = new String[] { }; + + if (intent != null && intent.hasExtra("input_values")) { + String[] temp = intent.getStringExtra("input_values").split("(? -1) { + out.name("index").value(result.index); + } + if (result.values.size() > 0) { + out.name("values"); + out.beginArray(); + for (Value value : result.values) { + out.beginObject(); + out.name("index").value(value.index); + out.name("text").value(value.text); + out.endObject(); + } + out.endArray(); + } + if (!result.error.equals("")) { + out.name("error").value(result.error); + } + + out.endObject(); + out.flush(); + resultReturned = true; + } + }); + } + + + /** + * Factory for returning proper input method type that we received in our incoming intent + */ + static class InputMethodFactory { + + public static InputMethod get(final String type, final AppCompatActivity activity) { + + switch (type == null ? "" : type) { + case "confirm": + return new ConfirmInputMethod(activity); + case "checkbox": + return new CheckBoxInputMethod(activity); + case "counter": + return new CounterInputMethod(activity); + case "date": + return new DateInputMethod(activity); + case "radio": + return new RadioInputMethod(activity); + case "sheet": + return new BottomSheetInputMethod(); + case "speech": + return new SpeechInputMethod(activity); + case "spinner": + return new SpinnerInputMethod(activity); + case "text": + return new TextInputMethod(activity); + case "time": + return new TimeInputMethod(activity); + default: + return (activity1, resultListener) -> { + InputResult result = new InputResult(); + result.error = "Unknown Input Method: " + type; + resultListener.onResult(result); + }; + } + } + } + + + /** + * Interface for creating an input method type + */ + interface InputMethod { + void create(AppCompatActivity activity, InputResultListener resultListener); + } + + + /** + * Callback interface for receiving an InputResult + */ + interface InputResultListener { + void onResult(InputResult result); + } + + + /** + * Simple POJO to store the result of input methods + */ + static class InputResult { + public String text = ""; + public String error = ""; + public int code = 0; + public static int index = -1; + public List values = new ArrayList<>(); + } + + + public static class Value { + public int index = -1; + public String text = ""; + } + + /* + * -------------------------------------- + * InputMethod Implementations + * -------------------------------------- + */ + + + /** + * CheckBox InputMethod + * Allow users to select multiple options from a range of values + */ + static class CheckBoxInputMethod extends InputDialog { + + CheckBoxInputMethod(AppCompatActivity activity) { + super(activity); + } + + @Override + LinearLayout createWidgetView(AppCompatActivity activity) { + LinearLayout layout = new LinearLayout(activity); + layout.setOrientation(LinearLayout.VERTICAL); + + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + layoutParams.topMargin = 32; + layoutParams.bottomMargin = 32; + + String[] values = getInputValues(activity.getIntent()); + + for (int j = 0; j < values.length; ++j) { + String value = values[j]; + + CheckBox checkBox = new CheckBox(activity); + checkBox.setText(value); + checkBox.setId(j); + checkBox.setTextSize(18); + checkBox.setPadding(16, 16, 16, 16); + checkBox.setLayoutParams(layoutParams); + + layout.addView(checkBox); + } + return layout; + } + + @Override + String getResult() { + int checkBoxCount = widgetView.getChildCount(); + + List values = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + sb.append("["); + + for (int j = 0; j < checkBoxCount; ++j) { + CheckBox box = widgetView.findViewById(j); + if (box.isChecked()) { + Value value = new Value(); + value.index = j; + value.text = box.getText().toString(); + values.add(value); + sb.append(box.getText().toString()).append(", "); + } + } + inputResult.values = values; + // remove trailing comma and add closing bracket + return sb.toString().replaceAll(", $", "") + "]"; + } + } + + + /** + * Confirm InputMethod + * Allow users to confirm YES or NO. + */ + static class ConfirmInputMethod extends InputDialog { + + ConfirmInputMethod(AppCompatActivity activity) { + super(activity); + } + + @Override + InputResult onDialogClick(int button) { + inputResult.text = button == Dialog.BUTTON_POSITIVE ? "yes" : "no"; + return inputResult; + } + + @Override + TextView createWidgetView(AppCompatActivity activity) { + TextView textView = new TextView(activity); + final Intent intent = activity.getIntent(); + + String text = intent.hasExtra("input_hint") ? intent.getStringExtra("input_hint") : "Confirm"; + textView.setText(text); + return textView; + } + + @Override + String getNegativeButtonText() { + return "No"; + } + + @Override + String getPositiveButtonText() { + return "Yes"; + } + } + + + /** + * Counter InputMethod + * Allow users to increment or decrement a number in a given range + */ + static class CounterInputMethod extends InputDialog { + static final int DEFAULT_MIN = 0; + static final int DEFAULT_MAX = 100; + static final int RANGE_LENGTH = 3; + + int min; + int max; + int counter; + + TextView counterLabel; + + CounterInputMethod(AppCompatActivity activity) { + super(activity); + } + + @Override + View createWidgetView(AppCompatActivity activity) { + View layout = View.inflate(activity, R.layout.dialog_counter, null); + counterLabel = layout.findViewById(R.id.counterTextView); + + final Button incrementButton = layout.findViewById(R.id.incrementButton); + incrementButton.setOnClickListener(view -> increment()); + + final Button decrementButton = layout.findViewById(R.id.decrementButton); + decrementButton.setOnClickListener(view -> decrement()); + updateCounterRange(); + + return layout; + } + + void updateCounterRange() { + final Intent intent = activity.getIntent(); + + if (intent.hasExtra("input_range")) { + int[] values = intent.getIntArrayExtra("input_range"); + if (values.length != RANGE_LENGTH) { + inputResult.error = "Invalid range! Must be 3 int values!"; + postCanceledResult(); + dialog.dismiss(); + } else { + min = Math.min(values[0], values[1]); + max = Math.max(values[0], values[1]); + counter = values[2]; + } + } else { + min = DEFAULT_MIN; + max = DEFAULT_MAX; + + // halfway + counter = (DEFAULT_MAX - DEFAULT_MIN) / 2; + } + updateLabel(); + } + + @Override + String getResult() { + return counterLabel.getText().toString(); + } + + void updateLabel() { + counterLabel.setText(String.valueOf(counter)); + } + + void increment() { + if ((counter + 1) <= max) { + ++counter; + updateLabel(); + } + } + + void decrement() { + if ((counter - 1) >= min) { + --counter; + updateLabel(); + } + } + } + + + /** + * Date InputMethod + * Allow users to pick a specific date + */ + static class DateInputMethod extends InputDialog { + + DateInputMethod(AppCompatActivity activity) { + super(activity); + } + + @Override + String getResult() { + int month = widgetView.getMonth(); + int day = widgetView.getDayOfMonth(); + int year = widgetView.getYear(); + + Calendar calendar = Calendar.getInstance(); + calendar.set(year, month, day, 0, 0, 0); + + final Intent intent = activity.getIntent(); + if (intent.hasExtra("date_format")) { + String date_format = intent.getStringExtra("date_format"); + try { + SimpleDateFormat dateFormat = new SimpleDateFormat(date_format); + dateFormat.setTimeZone(calendar.getTimeZone()); + return dateFormat.format(calendar.getTime()); + } catch (Exception e) { + inputResult.error = e.toString(); + postCanceledResult(); + } + } + return calendar.getTime().toString(); + } + + @Override + DatePicker createWidgetView(AppCompatActivity activity) { + return new DatePicker(activity); + } + } + + + /** + * Text InputMethod + * Allow users to enter plaintext or a password + */ + static class TextInputMethod extends InputDialog { + + TextInputMethod(AppCompatActivity activity) { + super(activity); + } + + @Override + String getResult() { + return widgetView.getText().toString(); + } + + @Override + EditText createWidgetView(AppCompatActivity activity) { + final Intent intent = activity.getIntent(); + EditText editText = new EditText(activity); + + if (intent.hasExtra("input_hint")) { + editText.setHint(intent.getStringExtra("input_hint")); + } + + boolean multiLine = intent.getBooleanExtra("multiple_lines", false); + boolean numeric = intent.getBooleanExtra("numeric", false); + boolean password = intent.getBooleanExtra("password", false); + + int flags = InputType.TYPE_CLASS_TEXT; + + if (password) { + flags = numeric ? (flags | InputType.TYPE_NUMBER_VARIATION_PASSWORD) : (flags | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + + if (multiLine) { + flags |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; + editText.setLines(4); + } + + if (numeric) { + flags &= ~InputType.TYPE_CLASS_TEXT; // clear to allow only numbers + flags |= InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL; + } + + editText.setInputType(flags); + + return editText; + } + } + + + /** + * Time InputMethod + * Allow users to pick a specific time + */ + static class TimeInputMethod extends InputDialog { + + TimeInputMethod(AppCompatActivity activity) { + super(activity); + } + + @Override + String getResult() { + String result = String.format(Locale.getDefault(), "%02d:%02d", widgetView.getHour(), widgetView.getMinute()); + return result; + } + + @Override + TimePicker createWidgetView(AppCompatActivity activity) { + return new TimePicker(activity); + } + } + + + /** + * Radio InputMethod + * Allow users to confirm from radio button options + */ + static class RadioInputMethod extends InputDialog { + RadioGroup radioGroup; + + RadioInputMethod(AppCompatActivity activity) { + super(activity); + } + + @Override + RadioGroup createWidgetView(AppCompatActivity activity) { + radioGroup = new RadioGroup(activity); + radioGroup.setPadding(16, 16, 16, 16); + + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + layoutParams.topMargin = 32; + layoutParams.bottomMargin = 32; + + String[] values = getInputValues(activity.getIntent()); + + for (int j = 0; j < values.length; ++j) { + String value = values[j]; + + RadioButton button = new RadioButton(activity); + button.setText(value); + button.setId(j); + button.setTextSize(18); + button.setPadding(16, 16, 16, 16); + button.setLayoutParams(layoutParams); + + radioGroup.addView(button); + } + return radioGroup; + } + + @Override + String getResult() { + int radioIndex = radioGroup.indexOfChild(widgetView.findViewById(radioGroup.getCheckedRadioButtonId())); + RadioButton radioButton = (RadioButton) radioGroup.getChildAt(radioIndex); + InputResult.index = radioIndex; + return (radioButton != null) ? radioButton.getText().toString() : ""; + } + } + + + /** + * BottomSheet InputMethod + * Allow users to select from a variety of options in a bottom sheet dialog + */ + public static class BottomSheetInputMethod extends BottomSheetDialogFragment implements InputMethod { + private InputResultListener resultListener; + + + @Override + public void create(AppCompatActivity activity, InputResultListener resultListener) { + this.resultListener = resultListener; + show(activity.getSupportFragmentManager(), "BOTTOM_SHEET"); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // create custom BottomSheetDialog that has friendlier dismissal behavior + return new BottomSheetDialog(getActivity(), getTheme()) { + @Override + public void onBackPressed() { + super.onBackPressed(); + // make it so that user only has to hit back key one time to get rid of bottom sheet + getActivity().onBackPressed(); + postCanceledResult(); + } + + @Override + public void cancel() { + super.cancel(); + + if (isCurrentAppTermux()) { + showKeyboard(); + } + // dismiss on single touch outside of dialog + getActivity().onBackPressed(); + postCanceledResult(); + } + }; + } + + @SuppressLint("RestrictedApi") + @Override + public void setupDialog(final Dialog dialog, int style) { + LinearLayout layout = new LinearLayout(getContext()); + layout.setMinimumHeight(100); + layout.setPadding(16, 16, 16, 16); + layout.setOrientation(LinearLayout.VERTICAL); + + NestedScrollView scrollView = new NestedScrollView(getContext()); + final String[] values = getInputValues(Objects.requireNonNull(getActivity()).getIntent()); + + for (int i = 0; i < values.length; ++i) { + final int j = i; + final TextView textView = new TextView(getContext()); + textView.setText(values[j]); + textView.setTextSize(20); + textView.setPadding(56, 56, 56, 56); + textView.setOnClickListener(view -> { + InputResult result = new InputResult(); + result.text = values[j]; + result.index = j; + dialog.dismiss(); + resultListener.onResult(result); + }); + + layout.addView(textView); + } + scrollView.addView(layout); + dialog.setContentView(scrollView); + hideKeyboard(); + } + + /** + * These keyboard methods exist to work around inconsistent show / hide behavior + * from canceling BottomSheetDialog and produces the desired result of hiding keyboard + * on creation of dialog and showing it after a selection or cancellation, as long as + * we are still within the Termux application + */ + + protected void hideKeyboard() { + getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + } + + protected void showKeyboard() { + getInputMethodManager().showSoftInput(getView(), InputMethodManager.SHOW_FORCED); + } + + protected InputMethodManager getInputMethodManager() { + return (InputMethodManager) Objects.requireNonNull(getContext()).getSystemService(Context.INPUT_METHOD_SERVICE); + } + + /** + * Checks to see if foreground application is Termux + */ + protected boolean isCurrentAppTermux() { + final ActivityManager activityManager = (ActivityManager) Objects.requireNonNull(getContext()).getSystemService(Context.ACTIVITY_SERVICE); + final List runningProcesses = Objects.requireNonNull(activityManager).getRunningAppProcesses(); + for (final ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { + if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + for (final String activeProcess : processInfo.pkgList) { + if (activeProcess.equals("com.termux")) { + return true; + } + } + } + } + return false; + } + + protected void postCanceledResult() { + InputResult result = new InputResult(); + result.code = Dialog.BUTTON_NEGATIVE; + resultListener.onResult(result); + } + } + + + /** + * Spinner InputMethod + * Allow users to make a selection based on a list of specified values + */ + static class SpinnerInputMethod extends InputDialog { + + SpinnerInputMethod(AppCompatActivity activity) { + super(activity); + } + + @Override + String getResult() { + InputResult.index = widgetView.getSelectedItemPosition(); + return widgetView.getSelectedItem().toString(); + } + + @Override + Spinner createWidgetView(AppCompatActivity activity) { + Spinner spinner = new Spinner(activity); + + final Intent intent = activity.getIntent(); + final String[] items = getInputValues(intent); + final ArrayAdapter adapter = new ArrayAdapter<>(activity, R.layout.spinner_item, items); + + spinner.setAdapter(adapter); + return spinner; + } + } + + + /** + * Speech InputMethod + * Allow users to use the built in microphone to get text from speech + */ + static class SpeechInputMethod extends InputDialog { + + SpeechInputMethod(AppCompatActivity activity) { + super(activity); + } + + @Override + TextView createWidgetView(AppCompatActivity activity) { + TextView textView = new TextView(activity); + final Intent intent = activity.getIntent(); + + String text = intent.hasExtra("input_hint") ? intent.getStringExtra("input_hint") : "Listening for speech..."; + + textView.setText(text); + textView.setTextSize(20); + return textView; + } + + @Override + public void create(final AppCompatActivity activity, final InputResultListener resultListener) { + // Since we're using the microphone, we need to make sure we have proper permission + if (!TermuxApiPermissionActivity.checkAndRequestPermissions(activity, activity.getIntent(), Manifest.permission.RECORD_AUDIO)) { + activity.finish(); + } + + if (!hasSpeechRecognizer(activity)) { + Toast.makeText(activity, "No voice recognition found!", Toast.LENGTH_SHORT).show(); + activity.finish(); + } + + + Intent speechIntent = createSpeechIntent(); + final SpeechRecognizer recognizer = createSpeechRecognizer(activity, resultListener); + + // create intermediate InputResultListener so that we can stop our speech listening + // if user hits the cancel button + DialogInterface.OnClickListener clickListener = getClickListener(result -> { + recognizer.stopListening(); + resultListener.onResult(result); + }); + + Dialog dialog = getDialogBuilder(activity, clickListener) + .setPositiveButton(null, null) + .setOnDismissListener(null) + .create(); + + dialog.setCanceledOnTouchOutside(false); + dialog.show(); + + recognizer.startListening(speechIntent); + } + + private boolean hasSpeechRecognizer(Context context) { + List installList = context.getPackageManager().queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0); + return !installList.isEmpty(); + } + + private Intent createSpeechIntent() { + Intent speechIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + speechIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1); + speechIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + return speechIntent; + } + + private SpeechRecognizer createSpeechRecognizer(AppCompatActivity activity, final InputResultListener listener) { + SpeechRecognizer recognizer = SpeechRecognizer.createSpeechRecognizer(activity); + recognizer.setRecognitionListener(new RecognitionListener() { + + @Override + public void onResults(Bundle results) { + List voiceResults = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); + + if (voiceResults != null && voiceResults.size() > 0) { + inputResult.text = voiceResults.get(0); + } + listener.onResult(inputResult); + } + + /** + * Get string description for error code + */ + @Override + public void onError(int error) { + String errorDescription; + + switch (error) { + case SpeechRecognizer.ERROR_AUDIO: + errorDescription = "ERROR_AUDIO"; + break; + case SpeechRecognizer.ERROR_CLIENT: + errorDescription = "ERROR_CLIENT"; + break; + case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS: + errorDescription = "ERROR_INSUFFICIENT_PERMISSIONS"; + break; + case SpeechRecognizer.ERROR_NETWORK: + errorDescription = "ERROR_NETWORK"; + break; + case SpeechRecognizer.ERROR_NETWORK_TIMEOUT: + errorDescription = "ERROR_NETWORK_TIMEOUT"; + break; + case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: + errorDescription = "ERROR_SPEECH_TIMEOUT"; + break; + default: + errorDescription = "ERROR_UNKNOWN"; + break; + } + inputResult.error = errorDescription; + listener.onResult(inputResult); + } + + + // unused + @Override + public void onEndOfSpeech() { } + + @Override + public void onReadyForSpeech(Bundle bundle) { } + + @Override + public void onBeginningOfSpeech() { } + + @Override + public void onRmsChanged(float v) { } + + @Override + public void onBufferReceived(byte[] bytes) { } + + @Override + public void onPartialResults(Bundle bundle) { } + + @Override + public void onEvent(int i, Bundle bundle) { } + }); + return recognizer; + } + } + + + /** + * Base Dialog class to extend from for adding specific views / widgets to a Dialog interface + * @param Main view type that will be displayed within dialog + */ + abstract static class InputDialog implements InputMethod { + // result that belongs to us + InputResult inputResult = new InputResult(); + + // listener for our input result + InputResultListener resultListener; + + // view that will be placed in our dialog + T widgetView; + + // dialog that holds everything + Dialog dialog; + + // our activity context + AppCompatActivity activity; + + + // method to be implemented that handles creating view that is placed in our dialog + abstract T createWidgetView(AppCompatActivity activity); + + // method that should be implemented that handles returning a result obtained through user input + String getResult() { + return null; + } + + + InputDialog(AppCompatActivity activity) { + this.activity = activity; + widgetView = createWidgetView(activity); + initActivityDisplay(activity); + } + + + @Override + public void create(AppCompatActivity activity, final InputResultListener resultListener) { + this.resultListener = resultListener; + + // Handle OK and Cancel button clicks + DialogInterface.OnClickListener clickListener = getClickListener(resultListener); + + // Dialog interface that will display to user + dialog = getDialogBuilder(activity, clickListener).create(); + dialog.show(); + } + + void postCanceledResult() { + inputResult.code = Dialog.BUTTON_NEGATIVE; + resultListener.onResult(inputResult); + } + + void initActivityDisplay(Activity activity) { + activity.setFinishOnTouchOutside(false); + activity.requestWindowFeature(Window.FEATURE_NO_TITLE); + } + + /** + * Places our generic widget view type inside a FrameLayout + */ + View getLayoutView(AppCompatActivity activity, T view) { + FrameLayout layout = getFrameLayout(activity); + ViewGroup.LayoutParams params = layout.getLayoutParams(); + + view.setLayoutParams(params); + layout.addView(view); + layout.setScrollbarFadingEnabled(false); + + // wrap everything in scrollview + ScrollView scrollView = new ScrollView(activity); + scrollView.addView(layout); + + return scrollView; + } + + DialogInterface.OnClickListener getClickListener(final InputResultListener listener) { + return (dialogInterface, button) -> { + InputResult result = onDialogClick(button); + listener.onResult(result); + }; + } + + DialogInterface.OnDismissListener getDismissListener() { + return dialogInterface -> { + // force dismiss behavior on single tap outside of dialog + activity.onBackPressed(); + onDismissed(); + }; + } + + /** + * Creates a dialog builder to initialize a dialog w/ a view and button click listeners + */ + AlertDialog.Builder getDialogBuilder(AppCompatActivity activity, DialogInterface.OnClickListener clickListener) { + final Intent intent = activity.getIntent(); + final View layoutView = getLayoutView(activity, widgetView); + + return new AlertDialog.Builder(activity) + .setTitle(intent.hasExtra("input_title") ? intent.getStringExtra("input_title") : "") + .setNegativeButton(getNegativeButtonText(), clickListener) + .setPositiveButton(getPositiveButtonText(), clickListener) + .setOnDismissListener(getDismissListener()) + .setView(layoutView); + + } + + String getNegativeButtonText() { + return "Cancel"; + } + + String getPositiveButtonText() { + return "OK"; + } + + void onDismissed() { + postCanceledResult(); + } + + /** + * Create a basic frame layout that will add a margin around our main widget view + */ + FrameLayout getFrameLayout(AppCompatActivity activity) { + FrameLayout layout = new FrameLayout(activity); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + final int margin = 56; + params.setMargins(margin, margin, margin, margin); + + params.setMargins(56, 56, 56, 56); + layout.setLayoutParams(params); + return layout; + } + + /** + * Returns an InputResult containing code of our button and the text if we hit OK + */ + InputResult onDialogClick(int button) { + // receive indication of whether the OK or CANCEL button is clicked + inputResult.code = button; + + // OK clicked + if (button == Dialog.BUTTON_POSITIVE) { + inputResult.text = getResult(); + } + return inputResult; + } + } + } + +} diff --git a/app/src/main/java/com/termux/api/DownloadAPI.java b/app/src/main/java/com/termux/api/apis/DownloadAPI.java similarity index 87% rename from app/src/main/java/com/termux/api/DownloadAPI.java rename to app/src/main/java/com/termux/api/apis/DownloadAPI.java index edda3448a..c34adbb2c 100644 --- a/app/src/main/java/com/termux/api/DownloadAPI.java +++ b/app/src/main/java/com/termux/api/apis/DownloadAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.DownloadManager; import android.app.DownloadManager.Request; @@ -6,13 +6,14 @@ import android.content.Intent; import android.net.Uri; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import java.io.File; public class DownloadAPI { - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, out -> { final Uri downloadUri = intent.getData(); if (downloadUri == null) { diff --git a/app/src/main/java/com/termux/api/FingerprintAPI.java b/app/src/main/java/com/termux/api/apis/FingerprintAPI.java similarity index 98% rename from app/src/main/java/com/termux/api/FingerprintAPI.java rename to app/src/main/java/com/termux/api/apis/FingerprintAPI.java index dc712c741..e76648739 100644 --- a/app/src/main/java/com/termux/api/FingerprintAPI.java +++ b/app/src/main/java/com/termux/api/apis/FingerprintAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.annotation.TargetApi; import android.content.Context; @@ -66,7 +66,7 @@ public class FingerprintAPI { /** * Handles setup of fingerprint sensor and writes Fingerprint result to console */ - static void onReceive(final Context context, final Intent intent) { + public static void onReceive(final Context context, final Intent intent) { resetFingerprintResult(); FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(context); diff --git a/app/src/main/java/com/termux/api/InfraredAPI.java b/app/src/main/java/com/termux/api/apis/InfraredAPI.java similarity index 89% rename from app/src/main/java/com/termux/api/InfraredAPI.java rename to app/src/main/java/com/termux/api/apis/InfraredAPI.java index 42d8ffcaa..f73832557 100644 --- a/app/src/main/java/com/termux/api/InfraredAPI.java +++ b/app/src/main/java/com/termux/api/apis/InfraredAPI.java @@ -1,10 +1,11 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; import android.hardware.ConsumerIrManager; import android.util.JsonWriter; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; /** @@ -12,7 +13,7 @@ */ public class InfraredAPI { - static void onReceiveCarrierFrequency(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveCarrierFrequency(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { @@ -40,7 +41,7 @@ public void writeJson(JsonWriter out) throws Exception { } - static void onReceiveTransmit(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveTransmit(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { diff --git a/app/src/main/java/com/termux/api/JobSchedulerAPI.java b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java similarity index 79% rename from app/src/main/java/com/termux/api/JobSchedulerAPI.java rename to app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java index 8d11c3709..812f8ae40 100644 --- a/app/src/main/java/com/termux/api/JobSchedulerAPI.java +++ b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java @@ -1,16 +1,20 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.job.JobInfo; +import android.app.job.JobParameters; import android.app.job.JobScheduler; +import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Build; import android.os.PersistableBundle; import androidx.annotation.RequiresApi; import android.text.TextUtils; import android.util.Log; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import java.io.File; @@ -54,7 +58,7 @@ private static String formatJobInfo(JobInfo jobInfo) { TextUtils.join(" ", description)); } - static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { final String scriptPath = intent.getStringExtra("script"); @@ -200,4 +204,47 @@ private static void cancelJob(TermuxApiReceiver apiReceiver, Intent intent, JobS } + + + public static class SchedulerJobService extends JobService { + + private static final String LOG_TAG = "TermuxAPISchedulerJob"; + public static final String SCRIPT_FILE_PATH = "com.termux.api.jobscheduler_script_path"; + + // Constants from TermuxService. + private static final String TERMUX_SERVICE = "com.termux.app.TermuxService"; + private static final String ACTION_EXECUTE = "com.termux.service_execute"; + private static final String EXTRA_EXECUTE_IN_BACKGROUND = "com.termux.execute.background"; + + @Override + public boolean onStartJob(JobParameters params) { + + Log.i(LOG_TAG, "Starting job " + params.toString()); + PersistableBundle extras = params.getExtras(); + String filePath = extras.getString(SCRIPT_FILE_PATH); + + Uri scriptUri = new Uri.Builder().scheme("com.termux.file").path(filePath).build(); + Intent executeIntent = new Intent(ACTION_EXECUTE, scriptUri); + executeIntent.setClassName("com.termux", TERMUX_SERVICE); + executeIntent.putExtra(EXTRA_EXECUTE_IN_BACKGROUND, true); + + Context context = getApplicationContext(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // https://developer.android.com/about/versions/oreo/background.html + context.startForegroundService(executeIntent); + } else { + context.startService(executeIntent); + } + + Log.i(LOG_TAG, "Started job " + params.toString()); + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + Log.i(LOG_TAG, "Stopped job " + params.toString()); + return false; + } + } + } diff --git a/app/src/main/java/com/termux/api/KeystoreAPI.java b/app/src/main/java/com/termux/api/apis/KeystoreAPI.java similarity index 98% rename from app/src/main/java/com/termux/api/KeystoreAPI.java rename to app/src/main/java/com/termux/api/apis/KeystoreAPI.java index b9c8ee8b1..0a2975a5b 100644 --- a/app/src/main/java/com/termux/api/KeystoreAPI.java +++ b/app/src/main/java/com/termux/api/apis/KeystoreAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.annotation.SuppressLint; import android.content.Intent; @@ -10,6 +10,7 @@ import android.util.Base64; import android.util.JsonWriter; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; import com.termux.api.util.ResultReturner.WithInput; @@ -35,12 +36,12 @@ import java.security.spec.RSAKeyGenParameterSpec; import java.util.Enumeration; -class KeystoreAPI { +public class KeystoreAPI { // this is the only provider name that is supported by Android private static final String PROVIDER = "AndroidKeyStore"; @SuppressLint("NewApi") - static void onReceive(TermuxApiReceiver apiReceiver, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, Intent intent) { switch (intent.getStringExtra("command")) { case "list": listKeys(apiReceiver, intent); diff --git a/app/src/main/java/com/termux/api/LocationAPI.java b/app/src/main/java/com/termux/api/apis/LocationAPI.java similarity index 97% rename from app/src/main/java/com/termux/api/LocationAPI.java rename to app/src/main/java/com/termux/api/apis/LocationAPI.java index 0b5ffd740..e58a11659 100644 --- a/app/src/main/java/com/termux/api/LocationAPI.java +++ b/app/src/main/java/com/termux/api/apis/LocationAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; @@ -12,6 +12,7 @@ import android.util.JsonWriter; import android.util.Log; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; import com.termux.api.util.TermuxApiLogger; @@ -24,7 +25,7 @@ public class LocationAPI { private static final String REQUEST_ONCE = "once"; private static final String REQUEST_UPDATES = "updates"; - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultJsonWriter() { @Override public void writeJson(final JsonWriter out) throws Exception { diff --git a/app/src/main/java/com/termux/api/MediaPlayerAPI.java b/app/src/main/java/com/termux/api/apis/MediaPlayerAPI.java similarity index 98% rename from app/src/main/java/com/termux/api/MediaPlayerAPI.java rename to app/src/main/java/com/termux/api/apis/MediaPlayerAPI.java index 8514d4a1a..a17ce8994 100644 --- a/app/src/main/java/com/termux/api/MediaPlayerAPI.java +++ b/app/src/main/java/com/termux/api/apis/MediaPlayerAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.Service; import android.content.Context; @@ -22,7 +22,7 @@ public class MediaPlayerAPI { /** * Starts our PlayerService */ - static void onReceive(final Context context, final Intent intent) { + public static void onReceive(final Context context, final Intent intent) { // Create intent for starting our player service and make sure // we retain all relevant info from this intent Intent playerService = new Intent(context, PlayerService.class); diff --git a/app/src/main/java/com/termux/api/MediaScannerAPI.java b/app/src/main/java/com/termux/api/apis/MediaScannerAPI.java similarity index 94% rename from app/src/main/java/com/termux/api/MediaScannerAPI.java rename to app/src/main/java/com/termux/api/apis/MediaScannerAPI.java index 218b44ed7..2cd08e70a 100644 --- a/app/src/main/java/com/termux/api/MediaScannerAPI.java +++ b/app/src/main/java/com/termux/api/apis/MediaScannerAPI.java @@ -1,9 +1,10 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; import android.media.MediaScannerConnection; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.TermuxApiLogger; @@ -14,7 +15,7 @@ public class MediaScannerAPI { - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { final String[] filePaths = intent.getStringArrayExtra("paths"); final boolean recursive = intent.getBooleanExtra("recursive", false); final Integer[] totalScanned = {0}; diff --git a/app/src/main/java/com/termux/api/MicRecorderAPI.java b/app/src/main/java/com/termux/api/apis/MicRecorderAPI.java similarity index 99% rename from app/src/main/java/com/termux/api/MicRecorderAPI.java rename to app/src/main/java/com/termux/api/apis/MicRecorderAPI.java index 5ba8a7825..70fe7787d 100644 --- a/app/src/main/java/com/termux/api/MicRecorderAPI.java +++ b/app/src/main/java/com/termux/api/apis/MicRecorderAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.Service; import android.content.Context; @@ -33,7 +33,7 @@ public class MicRecorderAPI { /** * Starts our MicRecorder service */ - static void onReceive(final Context context, final Intent intent) { + public static void onReceive(final Context context, final Intent intent) { Intent recorderService = new Intent(context, MicRecorderService.class); recorderService.setAction(intent.getAction()); recorderService.putExtras(intent.getExtras()); diff --git a/app/src/main/java/com/termux/api/apis/NfcAPI.java b/app/src/main/java/com/termux/api/apis/NfcAPI.java new file mode 100644 index 000000000..5903ea0e5 --- /dev/null +++ b/app/src/main/java/com/termux/api/apis/NfcAPI.java @@ -0,0 +1,303 @@ +package com.termux.api.apis; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.Ndef; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.JsonWriter; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.termux.api.util.ResultReturner; + +public class NfcAPI { + + public static void onReceive(final Context context, final Intent intent) { + context.startActivity(new Intent(context, NfcActivity.class).putExtras(intent.getExtras()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + + + + public static class NfcActivity extends AppCompatActivity { + private NfcAdapter adapter; + static String socket_input; + static String socket_output; + String mode; + String param; + String value; + //Check for NFC + protected void errorNfc(final Context context, Intent intent, String error) { + ResultReturner.returnData(context, intent, new ResultReturner.ResultJsonWriter() { + @Override + public void writeJson(JsonWriter out) throws Exception { + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); + out.beginObject(); + if (error.length() > 0) + out.name("error").value(error); + out.name("nfcPresent").value(null != adapter); + if(null!=adapter) + out.name("nfcActive").value(adapter.isEnabled()); + out.endObject(); + } + }); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = this.getIntent(); + if (intent != null) { + mode = intent.getStringExtra("mode"); + if (null == mode) + mode = "noData"; + param =intent.getStringExtra("param"); + if (null == param) + param = "noData"; + value=intent.getStringExtra("value"); + if (null == socket_input) socket_input = intent.getStringExtra("socket_input"); + if (null == socket_output) socket_output = intent.getStringExtra("socket_output"); + if (mode == "noData") { + errorNfc(this, intent,""); + finish(); + } + } + + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); + if((null==adapter)||(!adapter.isEnabled())){ + errorNfc(this,intent,""); + finish(); + } + } + + @Override + protected void onResume() { + super.onResume(); + adapter = NfcAdapter.getDefaultAdapter(this); + Intent intentNew = new Intent(this, NfcActivity.class).addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intentNew, 0); + IntentFilter[] intentFilter = new IntentFilter[]{ + new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED), + new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED), + new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)}; + adapter.enableForegroundDispatch(this, pendingIntent, intentFilter, null); + } + + @Override + protected void onNewIntent(Intent intent) { + intent.putExtra("socket_input", socket_input); + intent.putExtra("socket_output", socket_output); + + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + try { + postResult(this, intent); + } + catch (Exception e) + { + Log.e("Termix-api.NfcAction",e.getMessage()); + } + finish(); + } + super.onNewIntent(intent); + } + + @Override + protected void onPause() { + adapter.disableForegroundDispatch(this); + super.onPause(); + } + + @Override + protected void onDestroy() { + socket_input = null; + socket_output = null; + super.onDestroy(); + } + + protected void postResult(final Context context, Intent intent) { + ResultReturner.returnData(context, intent, new ResultReturner.ResultJsonWriter() { + @Override + public void writeJson(JsonWriter out) throws Exception { + Log.e("NFC","postResult"); + try + { + switch (mode) { + case "write": + switch (param) { + case "text": + Log.e("NFC","-->write"); + onReceiveNfcWrite(context, intent); + Log.e("NFC","<--write"); + break; + default: + onUnexpectedAction(out, "Wrong Params", "Should be text for TAG"); + break; + } + break; + case "read": + switch (param){ + case "short": + readNDEFTag(intent,out); + break; + case "full": + readFullNDEFTag(intent,out); + break; + case "noData": + readNDEFTag(intent,out); + break; + default: + onUnexpectedAction(out, "Wrong Params", "Should be correct param value"); + break; + } + break; + default: + onUnexpectedAction(out, "Wrong Params", "Should be correct mode value "); + break; + } + } + catch (Exception e){ + onUnexpectedAction(out, "exception", e.getMessage()); + } + } + }); + } + public void onReceiveNfcWrite( final Context context, Intent intent) throws Exception { + { + Log.e("NFC","---->onReceiveNfcWrite"); + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + NdefRecord record = NdefRecord.createTextRecord("en", value); + NdefMessage msg = new NdefMessage(new NdefRecord[]{record}); + Ndef ndef = Ndef.get(tag); + ndef.connect(); + ndef.writeNdefMessage(msg); + ndef.close(); + } + } + + + public void readNDEFTag(Intent intent, JsonWriter out) throws Exception { + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); + Parcelable[] msgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + Ndef ndefTag = Ndef.get(tag); + boolean bNdefPresent = false; + String strs[] = tag.getTechList(); + for (String s: strs){ + if (s.equals("android.nfc.tech.Ndef")) + bNdefPresent = true; + } + if (!bNdefPresent){ + onUnexpectedAction(out, "Wrong Technology","termux API support only NFEF Tag"); + return; + } + NdefMessage[] nmsgs = new NdefMessage[msgs.length]; + if (msgs.length == 1) { + nmsgs[0] = (NdefMessage) msgs[0]; + NdefRecord records[] = nmsgs[0].getRecords(); + out.beginObject(); + if (records.length >0 ) { + { + out.name("Record"); + if (records.length > 1) + out.beginArray(); + for (NdefRecord record: records){ + out.beginObject(); + int pos = 1 + record.getPayload()[0]; + pos = (NdefRecord.TNF_WELL_KNOWN==record.getTnf())?(int)record.getPayload()[0]+1:0; + int len = record.getPayload().length - pos; + byte msg[] = new byte[len]; + System.arraycopy(record.getPayload(), pos, msg, 0, len); + out.name("Payload").value(new String(msg)); + out.endObject(); + } + if (records.length > 1) + out.endArray(); + } + } + out.endObject(); + } + } + + public void readFullNDEFTag(Intent intent, JsonWriter out) throws Exception { + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + Ndef ndefTag = Ndef.get(tag); + Parcelable[] msgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); + + String strs[] = tag.getTechList(); + boolean bNdefPresent = false; + for (String s: strs){ + if (s.equals("android.nfc.tech.Ndef")) + bNdefPresent = true; + } + if (!bNdefPresent){ + onUnexpectedAction(out, "Wrong Technology","termux API support only NFEF Tag"); + return; + } + NdefMessage[] nmsgs = new NdefMessage[msgs.length]; + out.beginObject(); + { + byte[] tagID = tag.getId(); + StringBuilder sp = new StringBuilder(); + for (byte tagIDpart : tagID) { sp.append(String.format("%02x", tagIDpart)); } + out.name("id").value(sp.toString()); + out.name("typeTag").value(ndefTag.getType()); + out.name("maxSize").value(ndefTag.getMaxSize()); + out.name("techList"); + { + out.beginArray(); + String[] tlist = tag.getTechList(); + for (String str : tlist) { + out.value(str); + } + out.endArray(); + } + if (msgs.length == 1) { + Log.e("NFC", "-->> readFullNDEFTag - 06"); + nmsgs[0] = (NdefMessage) msgs[0]; + NdefRecord records[] = nmsgs[0].getRecords(); + { + out.name("record"); + if (records.length > 1) + out.beginArray(); + for (NdefRecord record : records) { + out.beginObject(); + out.name("type").value(new String(record.getType())); + out.name("tnf").value(record.getTnf()); + if (records[0].toUri() != null) out.name("URI").value(record.toUri().toString()); + out.name("mime").value(record.toMimeType()); + int pos = 1 + record.getPayload()[0]; + pos = (NdefRecord.TNF_WELL_KNOWN==record.getTnf())?(int)record.getPayload()[0]+1:0; + int len = record.getPayload().length - pos; + byte msg[] = new byte[len]; + System.arraycopy(record.getPayload(), pos, msg, 0, len); + out.name("payload").value(new String(msg)); + out.endObject(); + } + if (records.length > 1) out.endArray(); + } + } + + } + out.endObject(); + } + + protected void onUnexpectedAction(JsonWriter out,String error, String description) throws Exception { + out.beginObject(); + out.name("error").value(error); + out.name("description").value(description); + out.endObject(); + out.flush(); + } + } + +} diff --git a/app/src/main/java/com/termux/api/NotificationAPI.java b/app/src/main/java/com/termux/api/apis/NotificationAPI.java similarity index 97% rename from app/src/main/java/com/termux/api/NotificationAPI.java rename to app/src/main/java/com/termux/api/apis/NotificationAPI.java index 98664f092..60687a6bf 100644 --- a/app/src/main/java/com/termux/api/NotificationAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.Notification; import android.app.NotificationChannel; @@ -19,6 +19,8 @@ import androidx.core.app.RemoteInput; import androidx.core.util.Pair; +import com.termux.api.R; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.TermuxApiLogger; @@ -45,7 +47,7 @@ public class NotificationAPI { /** * Show a notification. Driven by the termux-show-notification script. */ - static void onReceiveShowNotification(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveShowNotification(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { Pair pair = buildNotification(context, intent); NotificationCompat.Builder notification = pair.first; String notificationId = pair.second; @@ -310,7 +312,7 @@ private static String getNotificationId(Intent intent) { return id; } - static void onReceiveRemoveNotification(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveRemoveNotification(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.noteDone(apiReceiver, intent); String notificationId = intent.getStringExtra("id"); if (notificationId != null) { @@ -371,7 +373,7 @@ static CharSequence shellEscape(CharSequence input) { return "\"" + input.toString().replace("\"", "\\\"") + "\""; } - static void onReceiveReplyToNotification(TermuxApiReceiver termuxApiReceiver, + public static void onReceiveReplyToNotification(TermuxApiReceiver termuxApiReceiver, Context context, Intent intent) { String replyKey = intent.getStringExtra("replyKey"); CharSequence reply = getMessageText(intent); diff --git a/app/src/main/java/com/termux/api/NotificationListAPI.java b/app/src/main/java/com/termux/api/apis/NotificationListAPI.java similarity index 86% rename from app/src/main/java/com/termux/api/NotificationListAPI.java rename to app/src/main/java/com/termux/api/apis/NotificationListAPI.java index 778335738..f111e541a 100644 --- a/app/src/main/java/com/termux/api/NotificationListAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationListAPI.java @@ -1,14 +1,16 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.Notification; import android.content.Context; import android.content.Intent; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.JsonWriter; import java.text.SimpleDateFormat; import java.util.Date; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; @@ -85,5 +87,26 @@ static void listNotifications(Context context, JsonWriter out) throws Exception out.endObject(); } out.endArray(); + } + + + + public static class NotificationService extends NotificationListenerService { + static NotificationService _this; + + public static NotificationService get() { + return _this; + } + + @Override + public void onListenerConnected() { + _this = this; + } + + @Override + public void onListenerDisconnected() { + _this = null; } } + +} diff --git a/app/src/main/java/com/termux/api/PhotoAPI.java b/app/src/main/java/com/termux/api/apis/PhotoAPI.java similarity index 98% rename from app/src/main/java/com/termux/api/PhotoAPI.java rename to app/src/main/java/com/termux/api/apis/PhotoAPI.java index f34488000..285115310 100644 --- a/app/src/main/java/com/termux/api/PhotoAPI.java +++ b/app/src/main/java/com/termux/api/apis/PhotoAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; @@ -20,6 +20,7 @@ import android.view.Surface; import android.view.WindowManager; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.TermuxApiLogger; @@ -36,7 +37,7 @@ public class PhotoAPI { - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { final String filePath = intent.getStringExtra("file"); final File outputFile = new File(filePath); final File outputDir = outputFile.getParentFile(); diff --git a/app/src/main/java/com/termux/api/SAFAPI.java b/app/src/main/java/com/termux/api/apis/SAFAPI.java similarity index 98% rename from app/src/main/java/com/termux/api/SAFAPI.java rename to app/src/main/java/com/termux/api/apis/SAFAPI.java index c1d8e0d45..201bb1cb1 100644 --- a/app/src/main/java/com/termux/api/SAFAPI.java +++ b/app/src/main/java/com/termux/api/apis/SAFAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; @@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.documentfile.provider.DocumentFile; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.TermuxApiLogger; @@ -62,8 +63,8 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten finish(); } } - - static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + + public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { String method = intent.getStringExtra("safmethod"); if (method == null) { TermuxApiLogger.error("safmethod extra null"); diff --git a/app/src/main/java/com/termux/api/SensorAPI.java b/app/src/main/java/com/termux/api/apis/SensorAPI.java similarity index 99% rename from app/src/main/java/com/termux/api/SensorAPI.java rename to app/src/main/java/com/termux/api/apis/SensorAPI.java index d0e9a0cde..f2e18099a 100644 --- a/app/src/main/java/com/termux/api/SensorAPI.java +++ b/app/src/main/java/com/termux/api/apis/SensorAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.Service; import android.content.Context; diff --git a/app/src/main/java/com/termux/api/ShareAPI.java b/app/src/main/java/com/termux/api/apis/ShareAPI.java similarity index 97% rename from app/src/main/java/com/termux/api/ShareAPI.java rename to app/src/main/java/com/termux/api/apis/ShareAPI.java index 407611f1c..c2fc5fcb1 100644 --- a/app/src/main/java/com/termux/api/ShareAPI.java +++ b/app/src/main/java/com/termux/api/apis/ShareAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.ContentValues; import android.content.Context; @@ -11,6 +11,8 @@ import android.text.TextUtils; import android.webkit.MimeTypeMap; +import com.termux.api.R; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.TermuxApiLogger; @@ -20,7 +22,7 @@ public class ShareAPI { - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { final String fileExtra = intent.getStringExtra("file"); final String titleExtra = intent.getStringExtra("title"); final String contentTypeExtra = intent.getStringExtra("content-type"); diff --git a/app/src/main/java/com/termux/api/SmsInboxAPI.java b/app/src/main/java/com/termux/api/apis/SmsInboxAPI.java similarity index 97% rename from app/src/main/java/com/termux/api/SmsInboxAPI.java rename to app/src/main/java/com/termux/api/apis/SmsInboxAPI.java index baad316b0..ae9a48fff 100644 --- a/app/src/main/java/com/termux/api/SmsInboxAPI.java +++ b/app/src/main/java/com/termux/api/apis/SmsInboxAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.annotation.SuppressLint; import android.content.ContentResolver; @@ -14,6 +14,7 @@ import android.util.JsonWriter; import android.util.Log; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; @@ -36,7 +37,7 @@ public class SmsInboxAPI { private static final String[] DISPLAY_NAME_PROJECTION = {PhoneLookup.DISPLAY_NAME}; - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { final int offset = intent.getIntExtra("offset", 0); final int limit = intent.getIntExtra("limit", 10); final String number = intent.hasExtra("from") ? intent.getStringExtra("from"):""; diff --git a/app/src/main/java/com/termux/api/SmsSendAPI.java b/app/src/main/java/com/termux/api/apis/SmsSendAPI.java similarity index 93% rename from app/src/main/java/com/termux/api/SmsSendAPI.java rename to app/src/main/java/com/termux/api/apis/SmsSendAPI.java index 78fdce455..43457c433 100644 --- a/app/src/main/java/com/termux/api/SmsSendAPI.java +++ b/app/src/main/java/com/termux/api/apis/SmsSendAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; @@ -6,6 +6,7 @@ import android.telephony.SubscriptionManager; import android.telephony.SubscriptionInfo; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.TermuxApiLogger; @@ -14,7 +15,7 @@ public class SmsSendAPI { - static void onReceive(TermuxApiReceiver apiReceiver, Context context, final Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.WithStringInput() { @Override public void writeResult(PrintWriter out) { diff --git a/app/src/main/java/com/termux/api/SpeechToTextAPI.java b/app/src/main/java/com/termux/api/apis/SpeechToTextAPI.java similarity index 99% rename from app/src/main/java/com/termux/api/SpeechToTextAPI.java rename to app/src/main/java/com/termux/api/apis/SpeechToTextAPI.java index 483e5c125..be0de6747 100644 --- a/app/src/main/java/com/termux/api/SpeechToTextAPI.java +++ b/app/src/main/java/com/termux/api/apis/SpeechToTextAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.Activity; import android.app.AlertDialog; diff --git a/app/src/main/java/com/termux/api/StorageGetAPI.java b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java similarity index 94% rename from app/src/main/java/com/termux/api/StorageGetAPI.java rename to app/src/main/java/com/termux/api/apis/StorageGetAPI.java index 2cd769980..f952cdc3f 100644 --- a/app/src/main/java/com/termux/api/StorageGetAPI.java +++ b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java @@ -1,10 +1,11 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.TermuxApiLogger; @@ -18,7 +19,7 @@ public class StorageGetAPI { private static final String FILE_EXTRA = "com.termux.api.storage.file"; - static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, out -> { final String fileExtra = intent.getStringExtra("file"); if (fileExtra == null || !new File(fileExtra).getParentFile().canWrite()) { diff --git a/app/src/main/java/com/termux/api/TelephonyAPI.java b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java similarity index 97% rename from app/src/main/java/com/termux/api/TelephonyAPI.java rename to app/src/main/java/com/termux/api/apis/TelephonyAPI.java index ee23aa633..5747a7f1b 100644 --- a/app/src/main/java/com/termux/api/TelephonyAPI.java +++ b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.annotation.SuppressLint; import android.content.Context; @@ -18,6 +18,7 @@ import android.util.JsonWriter; import android.util.Log; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import java.io.IOException; @@ -45,7 +46,7 @@ private static void writeIfKnown(JsonWriter out, String name, int value[]) throw } } - static void onReceiveTelephonyCellInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveTelephonyCellInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { @@ -172,7 +173,7 @@ public void writeJson(JsonWriter out) throws Exception { } - static void onReceiveTelephonyDeviceInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveTelephonyDeviceInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @SuppressLint("HardwareIds") @Override @@ -377,7 +378,7 @@ public void writeJson(JsonWriter out) throws Exception { }); } - static void onReceiveTelephonyCall(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveTelephonyCall(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { String numberExtra = intent.getStringExtra("number"); if (numberExtra == null) { Log.e("termux-api", "No 'number extra"); diff --git a/app/src/main/java/com/termux/api/TextToSpeechAPI.java b/app/src/main/java/com/termux/api/apis/TextToSpeechAPI.java similarity index 99% rename from app/src/main/java/com/termux/api/TextToSpeechAPI.java rename to app/src/main/java/com/termux/api/apis/TextToSpeechAPI.java index 4fea86ba7..587f8f650 100644 --- a/app/src/main/java/com/termux/api/TextToSpeechAPI.java +++ b/app/src/main/java/com/termux/api/apis/TextToSpeechAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.IntentService; import android.content.Context; diff --git a/app/src/main/java/com/termux/api/ToastAPI.java b/app/src/main/java/com/termux/api/apis/ToastAPI.java similarity index 98% rename from app/src/main/java/com/termux/api/ToastAPI.java rename to app/src/main/java/com/termux/api/apis/ToastAPI.java index 5221b3561..1bb5eaeed 100644 --- a/app/src/main/java/com/termux/api/ToastAPI.java +++ b/app/src/main/java/com/termux/api/apis/ToastAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; diff --git a/app/src/main/java/com/termux/api/TorchAPI.java b/app/src/main/java/com/termux/api/apis/TorchAPI.java similarity index 97% rename from app/src/main/java/com/termux/api/TorchAPI.java rename to app/src/main/java/com/termux/api/apis/TorchAPI.java index 1ddb35bc5..89aa519a0 100644 --- a/app/src/main/java/com/termux/api/TorchAPI.java +++ b/app/src/main/java/com/termux/api/apis/TorchAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.annotation.TargetApi; import android.content.Context; @@ -10,6 +10,7 @@ import android.os.Build; import android.widget.Toast; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.TermuxApiLogger; diff --git a/app/src/main/java/com/termux/api/UsbAPI.java b/app/src/main/java/com/termux/api/apis/UsbAPI.java similarity index 97% rename from app/src/main/java/com/termux/api/UsbAPI.java rename to app/src/main/java/com/termux/api/apis/UsbAPI.java index d71f1181b..ad82428d8 100644 --- a/app/src/main/java/com/termux/api/UsbAPI.java +++ b/app/src/main/java/com/termux/api/apis/UsbAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -12,6 +12,7 @@ import android.util.JsonWriter; import android.util.SparseArray; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import java.io.IOException; @@ -25,7 +26,7 @@ public class UsbAPI { private static SparseArray openDevices = new SparseArray<>(); - static void onReceive(final TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceive(final TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { UsbDevice device; String action = intent.getAction(); if (action == null) { diff --git a/app/src/main/java/com/termux/api/VibrateAPI.java b/app/src/main/java/com/termux/api/apis/VibrateAPI.java similarity index 82% rename from app/src/main/java/com/termux/api/VibrateAPI.java rename to app/src/main/java/com/termux/api/apis/VibrateAPI.java index 3c2cb88f5..54c5a0100 100644 --- a/app/src/main/java/com/termux/api/VibrateAPI.java +++ b/app/src/main/java/com/termux/api/apis/VibrateAPI.java @@ -1,15 +1,16 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; import android.media.AudioManager; import android.os.Vibrator; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; public class VibrateAPI { - static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); int milliseconds = intent.getIntExtra("duration_ms", 1000); boolean force = intent.getBooleanExtra("force", false); diff --git a/app/src/main/java/com/termux/api/VolumeAPI.java b/app/src/main/java/com/termux/api/apis/VolumeAPI.java similarity index 96% rename from app/src/main/java/com/termux/api/VolumeAPI.java rename to app/src/main/java/com/termux/api/apis/VolumeAPI.java index 6a7e4cbf4..4eae33602 100644 --- a/app/src/main/java/com/termux/api/VolumeAPI.java +++ b/app/src/main/java/com/termux/api/apis/VolumeAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.content.Context; import android.content.Intent; @@ -6,6 +6,7 @@ import android.util.JsonWriter; import android.util.SparseArray; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import java.io.IOException; @@ -25,7 +26,7 @@ public class VolumeAPI { } - static void onReceive(final TermuxApiReceiver receiver, final Context context, final Intent intent) { + public static void onReceive(final TermuxApiReceiver receiver, final Context context, final Intent intent) { final AudioManager audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); String action = intent.getAction(); diff --git a/app/src/main/java/com/termux/api/WallpaperAPI.java b/app/src/main/java/com/termux/api/apis/WallpaperAPI.java similarity index 98% rename from app/src/main/java/com/termux/api/WallpaperAPI.java rename to app/src/main/java/com/termux/api/apis/WallpaperAPI.java index 0d6f960ba..8a885ab23 100644 --- a/app/src/main/java/com/termux/api/WallpaperAPI.java +++ b/app/src/main/java/com/termux/api/apis/WallpaperAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.app.Service; import android.app.WallpaperManager; @@ -24,7 +24,7 @@ public class WallpaperAPI { - static void onReceive(final Context context, final Intent intent) { + public static void onReceive(final Context context, final Intent intent) { Intent wallpaperService = new Intent(context, WallpaperService.class); wallpaperService.putExtras(intent.getExtras()); context.startService(wallpaperService); diff --git a/app/src/main/java/com/termux/api/WifiAPI.java b/app/src/main/java/com/termux/api/apis/WifiAPI.java similarity index 93% rename from app/src/main/java/com/termux/api/WifiAPI.java rename to app/src/main/java/com/termux/api/apis/WifiAPI.java index 797577c58..f96b8ad0b 100644 --- a/app/src/main/java/com/termux/api/WifiAPI.java +++ b/app/src/main/java/com/termux/api/apis/WifiAPI.java @@ -1,4 +1,4 @@ -package com.termux.api; +package com.termux.api.apis; import android.annotation.SuppressLint; import android.content.Context; @@ -12,13 +12,14 @@ import android.text.format.Formatter; import android.util.JsonWriter; +import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import java.util.List; public class WifiAPI { - static void onReceiveWifiConnectionInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveWifiConnectionInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @SuppressLint("HardwareIds") @Override @@ -51,7 +52,7 @@ static boolean isLocationEnabled(Context context) { return lm.isProviderEnabled(LocationManager.GPS_PROVIDER); } - static void onReceiveWifiScanInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveWifiScanInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { @@ -112,7 +113,7 @@ public void writeJson(JsonWriter out) throws Exception { }); } - static void onReceiveWifiEnable(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + public static void onReceiveWifiEnable(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) { From cb5902943bd019cdf53a4022e10c68e65115ea65 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 12 Mar 2022 18:54:56 +0500 Subject: [PATCH 018/142] Changed: Rename TermuxAPIApplication setLogLevel() to setLogConfig() --- app/src/main/java/com/termux/api/TermuxAPIApplication.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/termux/api/TermuxAPIApplication.java b/app/src/main/java/com/termux/api/TermuxAPIApplication.java index 44433b449..0a8f74836 100644 --- a/app/src/main/java/com/termux/api/TermuxAPIApplication.java +++ b/app/src/main/java/com/termux/api/TermuxAPIApplication.java @@ -18,14 +18,14 @@ public void onCreate() { TermuxCrashUtils.setCrashHandler(this); // Set log config for the app - setLogLevel(getApplicationContext(), true); + setLogConfig(getApplicationContext(), true); Logger.logDebug("Starting Application"); SocketListener.createSocketListener(this); } - public static void setLogLevel(Context context, boolean commitToFile) { + public static void setLogConfig(Context context, boolean commitToFile) { Logger.setDefaultLogTag(TermuxConstants.TERMUX_API_APP_NAME.replaceAll(":", "")); // Load the log level from shared preferences and set it to the {@link Logger.CURRENT_LOG_LEVEL} From 2af9536222d509bbaa0c6c6b9fc29520f6d32911 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 12 Mar 2022 19:00:10 +0500 Subject: [PATCH 019/142] Changed: Use requireContext() and requireActivity() instead of requireNonNull() --- .../main/java/com/termux/api/apis/DialogAPI.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java index 3c866b38c..0ef74ad14 100644 --- a/app/src/main/java/com/termux/api/apis/DialogAPI.java +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -634,12 +634,12 @@ public void create(AppCompatActivity activity, InputResultListener resultListene @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // create custom BottomSheetDialog that has friendlier dismissal behavior - return new BottomSheetDialog(getActivity(), getTheme()) { + return new BottomSheetDialog(requireActivity(), getTheme()) { @Override public void onBackPressed() { super.onBackPressed(); // make it so that user only has to hit back key one time to get rid of bottom sheet - getActivity().onBackPressed(); + requireActivity().onBackPressed(); postCanceledResult(); } @@ -651,7 +651,7 @@ public void cancel() { showKeyboard(); } // dismiss on single touch outside of dialog - getActivity().onBackPressed(); + requireActivity().onBackPressed(); postCanceledResult(); } }; @@ -665,8 +665,8 @@ public void setupDialog(final Dialog dialog, int style) { layout.setPadding(16, 16, 16, 16); layout.setOrientation(LinearLayout.VERTICAL); - NestedScrollView scrollView = new NestedScrollView(getContext()); - final String[] values = getInputValues(Objects.requireNonNull(getActivity()).getIntent()); + NestedScrollView scrollView = new NestedScrollView(requireContext()); + final String[] values = getInputValues(requireActivity().getIntent()); for (int i = 0; i < values.length; ++i) { final int j = i; @@ -697,7 +697,7 @@ public void setupDialog(final Dialog dialog, int style) { */ protected void hideKeyboard() { - getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + Objects.requireNonNull(getDialog()).getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); } protected void showKeyboard() { @@ -705,14 +705,14 @@ protected void showKeyboard() { } protected InputMethodManager getInputMethodManager() { - return (InputMethodManager) Objects.requireNonNull(getContext()).getSystemService(Context.INPUT_METHOD_SERVICE); + return (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE); } /** * Checks to see if foreground application is Termux */ protected boolean isCurrentAppTermux() { - final ActivityManager activityManager = (ActivityManager) Objects.requireNonNull(getContext()).getSystemService(Context.ACTIVITY_SERVICE); + final ActivityManager activityManager = (ActivityManager) requireContext().getSystemService(Context.ACTIVITY_SERVICE); final List runningProcesses = Objects.requireNonNull(activityManager).getRunningAppProcesses(); for (final ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { From 78d76beb1b73d6a7f0f287941cd4722ec6b14a2d Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 12 Mar 2022 19:01:38 +0500 Subject: [PATCH 020/142] Fixed: Use equals() instead of == for nfc mode string comaparison --- app/src/main/java/com/termux/api/apis/NfcAPI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/termux/api/apis/NfcAPI.java b/app/src/main/java/com/termux/api/apis/NfcAPI.java index 5903ea0e5..d6081d7fc 100644 --- a/app/src/main/java/com/termux/api/apis/NfcAPI.java +++ b/app/src/main/java/com/termux/api/apis/NfcAPI.java @@ -65,7 +65,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { value=intent.getStringExtra("value"); if (null == socket_input) socket_input = intent.getStringExtra("socket_input"); if (null == socket_output) socket_output = intent.getStringExtra("socket_output"); - if (mode == "noData") { + if (mode.equals("noData")) { errorNfc(this, intent,""); finish(); } From 187e691cf957f8267ca215024a8691260bc0abd3 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 12 Mar 2022 23:46:56 +0500 Subject: [PATCH 021/142] Changed: Rename PlayerService to MediaPlayerService --- app/src/main/AndroidManifest.xml | 2 +- app/src/main/java/com/termux/api/apis/MediaPlayerAPI.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 241030820..43018c0ea 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -128,7 +128,7 @@ android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false" /> - Date: Sat, 12 Mar 2022 23:52:54 +0500 Subject: [PATCH 022/142] Changed: Rename SchedulerJobService to JobSchedulerService --- app/src/main/AndroidManifest.xml | 2 +- .../main/java/com/termux/api/apis/JobSchedulerAPI.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 43018c0ea..e9ee6952a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -124,7 +124,7 @@ android:exported="false" /> diff --git a/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java index 812f8ae40..79484cb06 100644 --- a/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java +++ b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java @@ -28,7 +28,7 @@ public class JobSchedulerAPI { private static String formatJobInfo(JobInfo jobInfo) { - final String path = jobInfo.getExtras().getString(SchedulerJobService.SCRIPT_FILE_PATH); + final String path = jobInfo.getExtras().getString(JobSchedulerService.SCRIPT_FILE_PATH); List description = new ArrayList(); if (jobInfo.isPeriodic()) { description.add(String.format(Locale.ENGLISH, "(periodic: %dms)", jobInfo.getIntervalMillis())); @@ -145,9 +145,9 @@ public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Int } PersistableBundle extras = new PersistableBundle(); - extras.putString(SchedulerJobService.SCRIPT_FILE_PATH, file.getAbsolutePath()); + extras.putString(JobSchedulerService.SCRIPT_FILE_PATH, file.getAbsolutePath()); - ComponentName serviceComponent = new ComponentName(context, SchedulerJobService.class); + ComponentName serviceComponent = new ComponentName(context, JobSchedulerService.class); JobInfo.Builder builder = new JobInfo.Builder(jobId, serviceComponent) .setExtras(extras) .setRequiredNetworkType(networkTypeCode) @@ -206,7 +206,7 @@ private static void cancelJob(TermuxApiReceiver apiReceiver, Intent intent, JobS - public static class SchedulerJobService extends JobService { + public static class JobSchedulerService extends JobService { private static final String LOG_TAG = "TermuxAPISchedulerJob"; public static final String SCRIPT_FILE_PATH = "com.termux.api.jobscheduler_script_path"; From 645e7ef37daa99cab5a4cd64d0149e0e89f16700 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 13 Mar 2022 01:06:20 +0500 Subject: [PATCH 023/142] Added: Use Logger class for logging and add log entries for all APIs for debugging --- .../java/com/termux/api/KeepAliveService.java | 10 ++- .../java/com/termux/api/SocketListener.java | 26 ++++---- .../com/termux/api/TermuxApiReceiver.java | 20 ++++-- .../TermuxApiPermissionActivity.java | 7 ++ .../java/com/termux/api/apis/AudioAPI.java | 5 ++ .../com/termux/api/apis/BatteryStatusAPI.java | 8 ++- .../com/termux/api/apis/BrightnessAPI.java | 5 ++ .../java/com/termux/api/apis/CallLogAPI.java | 5 ++ .../com/termux/api/apis/CameraInfoAPI.java | 5 ++ .../com/termux/api/apis/ClipboardAPI.java | 5 ++ .../com/termux/api/apis/ContactListAPI.java | 5 ++ .../java/com/termux/api/apis/DialogAPI.java | 16 ++++- .../java/com/termux/api/apis/DownloadAPI.java | 5 ++ .../com/termux/api/apis/FingerprintAPI.java | 15 +++-- .../java/com/termux/api/apis/InfraredAPI.java | 7 ++ .../com/termux/api/apis/JobSchedulerAPI.java | 16 +++-- .../java/com/termux/api/apis/KeystoreAPI.java | 6 ++ .../java/com/termux/api/apis/LocationAPI.java | 13 ++-- .../com/termux/api/apis/MediaPlayerAPI.java | 14 +++- .../com/termux/api/apis/MediaScannerAPI.java | 10 ++- .../com/termux/api/apis/MicRecorderAPI.java | 25 +++++-- .../main/java/com/termux/api/apis/NfcAPI.java | 66 ++++++++++++------- .../com/termux/api/apis/NotificationAPI.java | 16 ++++- .../termux/api/apis/NotificationListAPI.java | 4 ++ .../java/com/termux/api/apis/PhotoAPI.java | 42 ++++++------ .../main/java/com/termux/api/apis/SAFAPI.java | 43 +++++++----- .../java/com/termux/api/apis/SensorAPI.java | 37 +++++++---- .../java/com/termux/api/apis/ShareAPI.java | 21 +++++- .../java/com/termux/api/apis/SmsInboxAPI.java | 10 +-- .../java/com/termux/api/apis/SmsSendAPI.java | 12 ++-- .../com/termux/api/apis/SpeechToTextAPI.java | 25 +++++-- .../com/termux/api/apis/StorageGetAPI.java | 25 ++++++- .../com/termux/api/apis/TelephonyAPI.java | 14 +++- .../com/termux/api/apis/TextToSpeechAPI.java | 32 +++++++-- .../java/com/termux/api/apis/ToastAPI.java | 8 ++- .../java/com/termux/api/apis/TorchAPI.java | 12 ++-- .../main/java/com/termux/api/apis/UsbAPI.java | 5 ++ .../java/com/termux/api/apis/VibrateAPI.java | 5 ++ .../java/com/termux/api/apis/VolumeAPI.java | 4 ++ .../com/termux/api/apis/WallpaperAPI.java | 11 +++- .../java/com/termux/api/apis/WifiAPI.java | 10 ++- .../com/termux/api/util/ResultReturner.java | 6 +- .../com/termux/api/util/TermuxApiLogger.java | 21 ------ 43 files changed, 467 insertions(+), 190 deletions(-) delete mode 100644 app/src/main/java/com/termux/api/util/TermuxApiLogger.java diff --git a/app/src/main/java/com/termux/api/KeepAliveService.java b/app/src/main/java/com/termux/api/KeepAliveService.java index 666ca27aa..30b6eacc3 100644 --- a/app/src/main/java/com/termux/api/KeepAliveService.java +++ b/app/src/main/java/com/termux/api/KeepAliveService.java @@ -6,10 +6,16 @@ import androidx.annotation.Nullable; -public class KeepAliveService extends Service -{ +import com.termux.shared.logger.Logger; + +public class KeepAliveService extends Service { + + private static final String LOG_TAG = "KeepAliveService"; + @Override public int onStartCommand(Intent intent, int flags, int startId) { + Logger.logDebug(LOG_TAG, "onStartCommand"); + return Service.START_STICKY; } diff --git a/app/src/main/java/com/termux/api/SocketListener.java b/app/src/main/java/com/termux/api/SocketListener.java index 3da329a73..24980c7e8 100644 --- a/app/src/main/java/com/termux/api/SocketListener.java +++ b/app/src/main/java/com/termux/api/SocketListener.java @@ -5,7 +5,7 @@ import android.net.LocalServerSocket; import android.net.LocalSocket; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.BufferedWriter; import java.io.DataInputStream; @@ -17,8 +17,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class SocketListener -{ +public class SocketListener { + public static final String LISTEN_ADDRESS = "com.termux.api://listen"; private static final Pattern EXTRA_STRING = Pattern.compile("(-e|--es|--esa) +([^ ]+) +\"(.*?)(? { @@ -95,7 +97,7 @@ public static void createSocketListener(Application app) { } catch (NumberFormatException e) { String msg = "Invalid integer extra: " + m.group(0) + "\n"; - TermuxApiLogger.info(msg); + Logger.logInfo(LOG_TAG, msg); out.write(msg); err = true; break; @@ -110,7 +112,7 @@ public static void createSocketListener(Application app) { } catch (NumberFormatException e) { String msg = "Invalid float extra: " + m.group(0) + "\n"; - TermuxApiLogger.info(msg); + Logger.logInfo(LOG_TAG, msg); out.write(msg); err = true; break; @@ -130,7 +132,7 @@ public static void createSocketListener(Application app) { } catch (NumberFormatException e) { String msg = "Invalid int array extra: " + m.group(0) + "\n"; - TermuxApiLogger.info(msg); + Logger.logInfo(LOG_TAG, msg); out.write(msg); err = true; break; @@ -150,7 +152,7 @@ public static void createSocketListener(Application app) { } catch (NumberFormatException e) { String msg = "Invalid long array extra: " + m.group(0) + "\n"; - TermuxApiLogger.info(msg); + Logger.logInfo(LOG_TAG, msg); out.write(msg); err = true; break; @@ -167,7 +169,7 @@ public static void createSocketListener(Application app) { m = EXTRA_UNSUPPORTED.matcher(cmdline); if (m.find()) { String msg = "Unsupported argument type: " + m.group(0) + "\n"; - TermuxApiLogger.info(msg); + Logger.logInfo(LOG_TAG, msg); out.write(msg); err = true; } @@ -177,7 +179,7 @@ public static void createSocketListener(Application app) { cmdline = cmdline.replaceAll("\\s", ""); if (!"".equals(cmdline)) { String msg = "Unsupported options: " + cmdline + "\n"; - TermuxApiLogger.info(msg); + Logger.logInfo(LOG_TAG, msg); out.write(msg); err = true; } @@ -215,7 +217,7 @@ public static void createSocketListener(Application app) { con.getOutputStream().flush(); } catch (Exception e) { - TermuxApiLogger.error("Error parsing arguments", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error parsing arguments", e); out.write("Exception in the plugin\n"); out.flush(); } @@ -223,7 +225,7 @@ public static void createSocketListener(Application app) { } } catch (Exception e) { - TermuxApiLogger.error("Error listening for connections", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error listening for connections", e); } }); listener.start(); diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index 6f00595b5..a96f08b56 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -45,26 +45,32 @@ import com.termux.api.apis.VolumeAPI; import com.termux.api.apis.WallpaperAPI; import com.termux.api.apis.WifiAPI; -import com.termux.api.util.TermuxApiLogger; import com.termux.api.activities.TermuxApiPermissionActivity; +import com.termux.shared.data.IntentUtils; +import com.termux.shared.logger.Logger; public class TermuxApiReceiver extends BroadcastReceiver { + private static final String LOG_TAG = "TermuxApiReceiver"; + @Override public void onReceive(Context context, Intent intent) { + TermuxAPIApplication.setLogConfig(context, false); + Logger.logDebug(LOG_TAG, "Intent Received:\n" + IntentUtils.getIntentString(intent)); + try { doWork(context, intent); } catch (Exception e) { // Make sure never to throw exception from BroadCastReceiver to avoid "process is bad" // behaviour from the Android system. - TermuxApiLogger.error("Error in TermuxApiReceiver", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error in TermuxApiReceiver", e); } } private void doWork(Context context, Intent intent) { String apiMethod = intent.getStringExtra("api_method"); if (apiMethod == null) { - TermuxApiLogger.error("Missing 'api_method' extra"); + Logger.logError(LOG_TAG, "Missing 'api_method' extra"); return; } @@ -175,6 +181,9 @@ private void doWork(Context context, Intent intent) { case "NotificationReply": NotificationAPI.onReceiveReplyToNotification(this, context, intent); break; + case "SAF": + SAFAPI.onReceive(this, context, intent); + break; case "Sensor": SensorAPI.onReceive(context, intent); break; @@ -246,11 +255,8 @@ private void doWork(Context context, Intent intent) { case "WifiEnable": WifiAPI.onReceiveWifiEnable(this, context, intent); break; - case "SAF": - SAFAPI.onReceive(this, context, intent); - break; default: - TermuxApiLogger.error("Unrecognized 'api_method' extra: '" + apiMethod + "'"); + Logger.logError(LOG_TAG, "Unrecognized 'api_method' extra: '" + apiMethod + "'"); } } diff --git a/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java b/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java index ab9fa42e9..c6f54f4c1 100644 --- a/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java +++ b/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java @@ -8,11 +8,14 @@ import android.util.JsonWriter; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; import java.util.ArrayList; public class TermuxApiPermissionActivity extends Activity { + private static final String LOG_TAG = "TermuxApiPermissionActivity"; + /** * Intent extra containing the permissions to request. */ @@ -56,12 +59,16 @@ public void writeJson(JsonWriter out) throws Exception { @Override protected void onNewIntent(Intent intent) { + Logger.logDebug(LOG_TAG, "onNewIntent"); + super.onNewIntent(intent); setIntent(intent); } @Override protected void onResume() { + Logger.logVerbose(LOG_TAG, "onResume"); + super.onResume(); ArrayList permissionValues = getIntent().getStringArrayListExtra(PERMISSIONS_EXTRA); requestPermissions(permissionValues.toArray(new String[0]), 123); diff --git a/app/src/main/java/com/termux/api/apis/AudioAPI.java b/app/src/main/java/com/termux/api/apis/AudioAPI.java index 756cc67bf..703dd1b0f 100644 --- a/app/src/main/java/com/termux/api/apis/AudioAPI.java +++ b/app/src/main/java/com/termux/api/apis/AudioAPI.java @@ -10,10 +10,15 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; public class AudioAPI { + private static final String LOG_TAG = "AudioAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); final String SampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); final String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); diff --git a/app/src/main/java/com/termux/api/apis/BatteryStatusAPI.java b/app/src/main/java/com/termux/api/apis/BatteryStatusAPI.java index 7d504da3d..b18aa5436 100644 --- a/app/src/main/java/com/termux/api/apis/BatteryStatusAPI.java +++ b/app/src/main/java/com/termux/api/apis/BatteryStatusAPI.java @@ -9,11 +9,15 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; public class BatteryStatusAPI { + private static final String LOG_TAG = "BatteryStatusAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + ResultReturner.returnData(apiReceiver, intent, new ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { @@ -94,7 +98,7 @@ public void writeJson(JsonWriter out) throws Exception { batteryStatusString = "UNKNOWN"; break; default: - TermuxApiLogger.error("Invalid BatteryManager.EXTRA_STATUS value: " + status); + Logger.logError(LOG_TAG, "Invalid BatteryManager.EXTRA_STATUS value: " + status); batteryStatusString = "UNKNOWN"; } diff --git a/app/src/main/java/com/termux/api/apis/BrightnessAPI.java b/app/src/main/java/com/termux/api/apis/BrightnessAPI.java index 492bf3ca8..7bbeb5b8c 100644 --- a/app/src/main/java/com/termux/api/apis/BrightnessAPI.java +++ b/app/src/main/java/com/termux/api/apis/BrightnessAPI.java @@ -7,10 +7,15 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; public class BrightnessAPI { + private static final String LOG_TAG = "BrightnessAPI"; + public static void onReceive(final TermuxApiReceiver receiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + final ContentResolver contentResolver = context.getContentResolver(); if (intent.hasExtra("auto")) { boolean auto = intent.getBooleanExtra("auto", false); diff --git a/app/src/main/java/com/termux/api/apis/CallLogAPI.java b/app/src/main/java/com/termux/api/apis/CallLogAPI.java index 1658799c0..933a01888 100644 --- a/app/src/main/java/com/termux/api/apis/CallLogAPI.java +++ b/app/src/main/java/com/termux/api/apis/CallLogAPI.java @@ -8,6 +8,7 @@ import android.util.JsonWriter; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; import java.io.IOException; import java.text.DateFormat; @@ -20,7 +21,11 @@ */ public class CallLogAPI { + private static final String LOG_TAG = "CallLogAPI"; + public static void onReceive(final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + final int offset = intent.getIntExtra("offset", 0); final int limit = intent.getIntExtra("limit", 50); diff --git a/app/src/main/java/com/termux/api/apis/CameraInfoAPI.java b/app/src/main/java/com/termux/api/apis/CameraInfoAPI.java index 38300977e..bf27a0f4d 100644 --- a/app/src/main/java/com/termux/api/apis/CameraInfoAPI.java +++ b/app/src/main/java/com/termux/api/apis/CameraInfoAPI.java @@ -14,10 +14,15 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; +import com.termux.shared.logger.Logger; public class CameraInfoAPI { + private static final String LOG_TAG = "CameraInfoAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + ResultReturner.returnData(apiReceiver, intent, new ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { diff --git a/app/src/main/java/com/termux/api/apis/ClipboardAPI.java b/app/src/main/java/com/termux/api/apis/ClipboardAPI.java index d12be9797..83d4ab640 100644 --- a/app/src/main/java/com/termux/api/apis/ClipboardAPI.java +++ b/app/src/main/java/com/termux/api/apis/ClipboardAPI.java @@ -9,12 +9,17 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; import java.io.PrintWriter; public class ClipboardAPI { + private static final String LOG_TAG = "ClipboardAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); final ClipData clipData = clipboard.getPrimaryClip(); diff --git a/app/src/main/java/com/termux/api/apis/ContactListAPI.java b/app/src/main/java/com/termux/api/apis/ContactListAPI.java index 9b168d508..142e2a488 100644 --- a/app/src/main/java/com/termux/api/apis/ContactListAPI.java +++ b/app/src/main/java/com/termux/api/apis/ContactListAPI.java @@ -13,10 +13,15 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; +import com.termux.shared.logger.Logger; public class ContactListAPI { + private static final String LOG_TAG = "ContactListAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + ResultReturner.returnData(apiReceiver, intent, new ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java index 0ef74ad14..512a02f16 100644 --- a/app/src/main/java/com/termux/api/apis/DialogAPI.java +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -19,7 +19,6 @@ import android.speech.SpeechRecognizer; import android.text.InputType; import android.util.JsonWriter; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -49,6 +48,7 @@ import com.termux.api.R; import com.termux.api.util.ResultReturner; import com.termux.api.activities.TermuxApiPermissionActivity; +import com.termux.shared.logger.Logger; import java.io.File; import java.io.FileInputStream; @@ -67,7 +67,11 @@ */ public class DialogAPI { + private static final String LOG_TAG = "DialogAPI"; + public static void onReceive(final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + context.startActivity(new Intent(context, DialogActivity.class).putExtras(intent.getExtras()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } @@ -75,6 +79,8 @@ public static void onReceive(final Context context, final Intent intent) { public static class DialogActivity extends AppCompatActivity { + private static final String LOG_TAG = "DialogActivity"; + private boolean resultReturned = false; protected boolean getBlackUI() { @@ -95,7 +101,7 @@ protected boolean getBlackUI() { } mUseBlackUi = props.getProperty("use-black-ui").equals("true"); } catch (Exception e) { - Log.e("termux-api", "Error loading props", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error loading props", e); } } @@ -104,6 +110,8 @@ protected boolean getBlackUI() { @Override protected void onCreate(Bundle savedInstanceState) { + Logger.logDebug(LOG_TAG, "onCreate"); + super.onCreate(savedInstanceState); final Intent intent = getIntent(); final Context context = this; @@ -123,12 +131,16 @@ protected void onCreate(Bundle savedInstanceState) { @Override protected void onNewIntent(Intent intent) { + Logger.logDebug(LOG_TAG, "onNewIntent"); + super.onNewIntent(intent); setIntent(intent); } @Override protected void onDestroy() { + Logger.logDebug(LOG_TAG, "onDestroy"); + super.onDestroy(); if (!resultReturned) { diff --git a/app/src/main/java/com/termux/api/apis/DownloadAPI.java b/app/src/main/java/com/termux/api/apis/DownloadAPI.java index c34adbb2c..201e8094f 100644 --- a/app/src/main/java/com/termux/api/apis/DownloadAPI.java +++ b/app/src/main/java/com/termux/api/apis/DownloadAPI.java @@ -8,12 +8,17 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; import java.io.File; public class DownloadAPI { + private static final String LOG_TAG = "DownloadAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + ResultReturner.returnData(apiReceiver, intent, out -> { final Uri downloadUri = intent.getData(); if (downloadUri == null) { diff --git a/app/src/main/java/com/termux/api/apis/FingerprintAPI.java b/app/src/main/java/com/termux/api/apis/FingerprintAPI.java index e76648739..233f2044f 100644 --- a/app/src/main/java/com/termux/api/apis/FingerprintAPI.java +++ b/app/src/main/java/com/termux/api/apis/FingerprintAPI.java @@ -1,9 +1,7 @@ package com.termux.api.apis; -import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -16,7 +14,7 @@ import androidx.fragment.app.FragmentActivity; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.util.ArrayList; import java.util.List; @@ -29,6 +27,7 @@ * This API allows users to use device fingerprint sensor as an authentication mechanism */ public class FingerprintAPI { + protected static final String TAG = "FingerprintAPI"; protected static final String KEY_NAME = "TermuxFingerprintAPIKey"; protected static final String KEYSTORE_NAME = "AndroidKeyStore"; @@ -63,10 +62,14 @@ public class FingerprintAPI { protected static boolean postedResult = false; + private static final String LOG_TAG = "FingerprintAPI"; + /** * Handles setup of fingerprint sensor and writes Fingerprint result to console */ public static void onReceive(final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + resetFingerprintResult(); FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(context); @@ -136,8 +139,12 @@ protected static boolean validateFingerprintSensor(Context context, FingerprintM */ public static class FingerprintActivity extends FragmentActivity{ + private static final String LOG_TAG = "FingerprintActivity"; + @Override public void onCreate(Bundle savedInstanceState) { + Logger.logDebug(LOG_TAG, "onCreate"); + super.onCreate(savedInstanceState); handleFingerprint(); } @@ -167,7 +174,7 @@ public void onAuthenticationError(int errorCode, @NonNull CharSequence errString } setAuthResult(AUTH_RESULT_FAILURE); postFingerprintResult(context, intent, fingerprintResult); - TermuxApiLogger.error(errString.toString()); + Logger.logError(LOG_TAG, errString.toString()); } @Override diff --git a/app/src/main/java/com/termux/api/apis/InfraredAPI.java b/app/src/main/java/com/termux/api/apis/InfraredAPI.java index f73832557..249363088 100644 --- a/app/src/main/java/com/termux/api/apis/InfraredAPI.java +++ b/app/src/main/java/com/termux/api/apis/InfraredAPI.java @@ -7,13 +7,18 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; /** * Exposing {@link ConsumerIrManager}. */ public class InfraredAPI { + private static final String LOG_TAG = "InfraredAPI"; + public static void onReceiveCarrierFrequency(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveCarrierFrequency"); + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { @@ -42,6 +47,8 @@ public void writeJson(JsonWriter out) throws Exception { public static void onReceiveTransmit(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveTransmit"); + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { diff --git a/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java index 79484cb06..bcad38943 100644 --- a/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java +++ b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java @@ -12,10 +12,10 @@ import android.os.PersistableBundle; import androidx.annotation.RequiresApi; import android.text.TextUtils; -import android.util.Log; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; import java.io.File; import java.util.ArrayList; @@ -26,7 +26,6 @@ public class JobSchedulerAPI { private static final String LOG_TAG = "JobSchedulerAPI"; - private static String formatJobInfo(JobInfo jobInfo) { final String path = jobInfo.getExtras().getString(JobSchedulerService.SCRIPT_FILE_PATH); List description = new ArrayList(); @@ -59,6 +58,7 @@ private static String formatJobInfo(JobInfo jobInfo) { } public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); final String scriptPath = intent.getStringExtra("script"); @@ -169,7 +169,7 @@ public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Int final int scheduleResponse = jobScheduler.schedule(job); final String message = String.format(Locale.ENGLISH, "Scheduling %s - response %d", formatJobInfo(job), scheduleResponse); - Log.i(LOG_TAG, message); + Logger.logInfo(LOG_TAG, message); ResultReturner.returnData(apiReceiver, intent, out -> out.println(message)); @@ -208,7 +208,6 @@ private static void cancelJob(TermuxApiReceiver apiReceiver, Intent intent, JobS public static class JobSchedulerService extends JobService { - private static final String LOG_TAG = "TermuxAPISchedulerJob"; public static final String SCRIPT_FILE_PATH = "com.termux.api.jobscheduler_script_path"; // Constants from TermuxService. @@ -216,10 +215,12 @@ public static class JobSchedulerService extends JobService { private static final String ACTION_EXECUTE = "com.termux.service_execute"; private static final String EXTRA_EXECUTE_IN_BACKGROUND = "com.termux.execute.background"; + private static final String LOG_TAG = "JobSchedulerService"; + @Override public boolean onStartJob(JobParameters params) { + Logger.logInfo(LOG_TAG, "onStartJob: " + params.toString()); - Log.i(LOG_TAG, "Starting job " + params.toString()); PersistableBundle extras = params.getExtras(); String filePath = extras.getString(SCRIPT_FILE_PATH); @@ -236,13 +237,14 @@ public boolean onStartJob(JobParameters params) { context.startService(executeIntent); } - Log.i(LOG_TAG, "Started job " + params.toString()); + Logger.logInfo(LOG_TAG, "Job started for \"" + filePath + "\""); + return false; } @Override public boolean onStopJob(JobParameters params) { - Log.i(LOG_TAG, "Stopped job " + params.toString()); + Logger.logInfo(LOG_TAG, "onStopJob: " + params.toString()); return false; } } diff --git a/app/src/main/java/com/termux/api/apis/KeystoreAPI.java b/app/src/main/java/com/termux/api/apis/KeystoreAPI.java index 0a2975a5b..ffe008dd4 100644 --- a/app/src/main/java/com/termux/api/apis/KeystoreAPI.java +++ b/app/src/main/java/com/termux/api/apis/KeystoreAPI.java @@ -14,6 +14,7 @@ import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; import com.termux.api.util.ResultReturner.WithInput; +import com.termux.shared.logger.Logger; import java.io.ByteArrayOutputStream; import java.io.File; @@ -37,11 +38,16 @@ import java.util.Enumeration; public class KeystoreAPI { + + private static final String LOG_TAG = "KeystoreAPI"; + // this is the only provider name that is supported by Android private static final String PROVIDER = "AndroidKeyStore"; @SuppressLint("NewApi") public static void onReceive(TermuxApiReceiver apiReceiver, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + switch (intent.getStringExtra("command")) { case "list": listKeys(apiReceiver, intent); diff --git a/app/src/main/java/com/termux/api/apis/LocationAPI.java b/app/src/main/java/com/termux/api/apis/LocationAPI.java index e58a11659..cf70ea45c 100644 --- a/app/src/main/java/com/termux/api/apis/LocationAPI.java +++ b/app/src/main/java/com/termux/api/apis/LocationAPI.java @@ -10,22 +10,25 @@ import android.os.Looper; import android.os.SystemClock; import android.util.JsonWriter; -import android.util.Log; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.IOException; public class LocationAPI { + private static final String LOG_TAG = "LocationAPI"; + private static final String REQUEST_LAST_KNOWN = "last"; private static final String REQUEST_ONCE = "once"; private static final String REQUEST_UPDATES = "updates"; public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + ResultReturner.returnData(apiReceiver, intent, new ResultJsonWriter() { @Override public void writeJson(final JsonWriter out) throws Exception { @@ -75,7 +78,7 @@ public void onLocationChanged(Location location) { try { locationToJson(location, out); } catch (IOException e) { - TermuxApiLogger.error("Writing json", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Writing json", e); } finally { Looper.myLooper().quit(); } @@ -108,7 +111,7 @@ public void onLocationChanged(Location location) { locationToJson(location, out); out.flush(); } catch (IOException e) { - TermuxApiLogger.error("Writing json", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Writing json", e); } } }, null); @@ -119,7 +122,7 @@ public void run() { try { Thread.sleep(30 * 1000); } catch (InterruptedException e) { - Log.e("termux", "INTER", e); + Logger.logStackTraceWithMessage(LOG_TAG, "INTER", e); } looper.quit(); } diff --git a/app/src/main/java/com/termux/api/apis/MediaPlayerAPI.java b/app/src/main/java/com/termux/api/apis/MediaPlayerAPI.java index 015319d91..081bc8125 100644 --- a/app/src/main/java/com/termux/api/apis/MediaPlayerAPI.java +++ b/app/src/main/java/com/termux/api/apis/MediaPlayerAPI.java @@ -8,7 +8,7 @@ import android.os.PowerManager; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.File; import java.io.IOException; @@ -19,10 +19,14 @@ */ public class MediaPlayerAPI { + private static final String LOG_TAG = "MediaPlayerAPI"; + /** * Starts our MediaPlayerService */ public static void onReceive(final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + // Create intent for starting our player service and make sure // we retain all relevant info from this intent Intent playerService = new Intent(context, MediaPlayerService.class); @@ -65,6 +69,7 @@ public static class MediaPlayerService extends Service implements MediaPlayer.On protected static String trackName; + private static final String LOG_TAG = "MediaPlayerService"; /** * Returns our MediaPlayer instance and ensures it has all the necessary callbacks @@ -84,6 +89,8 @@ protected MediaPlayer getMediaPlayer() { * What we received from TermuxApiReceiver but now within this service */ public int onStartCommand(Intent intent, int flags, int startId) { + Logger.logDebug(LOG_TAG, "onStartCommand"); + String command = intent.getAction(); MediaPlayer player = getMediaPlayer(); Context context = getApplicationContext(); @@ -97,9 +104,10 @@ public int onStartCommand(Intent intent, int flags, int startId) { } public void onDestroy() { + Logger.logDebug(LOG_TAG, "onDestroy"); + super.onDestroy(); cleanUpMediaPlayer(); - TermuxApiLogger.info("MediaPlayerAPI PlayerService onDestroy()"); } /** @@ -120,7 +128,7 @@ public IBinder onBind(Intent intent) { @Override public boolean onError(MediaPlayer mediaPlayer, int what, int extra) { - TermuxApiLogger.error("MediaPlayerAPI error: " + what); + Logger.logVerbose(LOG_TAG, "onError: what: " + what + ", extra: " + extra); return false; } diff --git a/app/src/main/java/com/termux/api/apis/MediaScannerAPI.java b/app/src/main/java/com/termux/api/apis/MediaScannerAPI.java index 2cd08e70a..d911980dc 100644 --- a/app/src/main/java/com/termux/api/apis/MediaScannerAPI.java +++ b/app/src/main/java/com/termux/api/apis/MediaScannerAPI.java @@ -6,7 +6,7 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.File; import java.io.PrintWriter; @@ -15,7 +15,11 @@ public class MediaScannerAPI { + private static final String LOG_TAG = "MediaScannerAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + final String[] filePaths = intent.getStringArrayExtra("paths"); final boolean recursive = intent.getBooleanExtra("recursive", false); final Integer[] totalScanned = {0}; @@ -36,7 +40,7 @@ private static void scanFiles(PrintWriter out, Context context, String[] filePat context.getApplicationContext(), filePaths, null, - (path, uri) -> TermuxApiLogger.info("'" + path + "'" + (uri != null ? " -> '" + uri + "'" : ""))); + (path, uri) -> Logger.logInfo(LOG_TAG, "'" + path + "'" + (uri != null ? " -> '" + uri + "'" : ""))); if (verbose) for (String path : filePaths) { out.println(path); @@ -55,7 +59,7 @@ private static void scanFilesRecursively(PrintWriter out, Context context, Strin try { fileList = currentPath.listFiles(); } catch (SecurityException e) { - TermuxApiLogger.error(String.format("Failed to open '%s'", currentPath.toString()), e); + Logger.logStackTraceWithMessage(LOG_TAG, String.format("Failed to open '%s'", currentPath.toString()), e); } if (fileList != null && fileList.length > 0) { diff --git a/app/src/main/java/com/termux/api/apis/MicRecorderAPI.java b/app/src/main/java/com/termux/api/apis/MicRecorderAPI.java index 70fe7787d..a3360efdf 100644 --- a/app/src/main/java/com/termux/api/apis/MicRecorderAPI.java +++ b/app/src/main/java/com/termux/api/apis/MicRecorderAPI.java @@ -11,7 +11,7 @@ import android.util.SparseIntArray; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import org.json.JSONException; import org.json.JSONObject; @@ -30,10 +30,14 @@ */ public class MicRecorderAPI { + private static final String LOG_TAG = "MicRecorderAPI"; + /** * Starts our MicRecorder service */ public static void onReceive(final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + Intent recorderService = new Intent(context, MicRecorderService.class); recorderService.setAction(intent.getAction()); recorderService.putExtras(intent.getExtras()); @@ -58,11 +62,15 @@ public static class MicRecorderService extends Service implements MediaRecorder. protected static File file; + private static final String LOG_TAG = "MicRecorderService"; + public void onCreate() { getMediaRecorder(this); } public int onStartCommand(Intent intent, int flags, int startId) { + Logger.logDebug(LOG_TAG, "onStartCommand"); + // get command handler and display result String command = intent.getAction(); Context context = getApplicationContext(); @@ -115,8 +123,9 @@ protected static void getMediaRecorder(MicRecorderService service) { } public void onDestroy() { + Logger.logDebug(LOG_TAG, "onDestroy"); + cleanupMediaRecorder(); - TermuxApiLogger.info("MicRecorderAPI MicRecorderService onDestroy()"); } /** @@ -138,19 +147,21 @@ public IBinder onBind(Intent intent) { @Override public void onError(MediaRecorder mr, int what, int extra) { + Logger.logVerbose(LOG_TAG, "onError: what: " + what + ", extra: " + extra); + isRecording = false; this.stopSelf(); - TermuxApiLogger.error("MicRecorderService onError() " + what); } @Override public void onInfo(MediaRecorder mr, int what, int extra) { + Logger.logVerbose(LOG_TAG, "onInfo: what: " + what + ", extra: " + extra); + switch (what) { case MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED: // intentional fallthrough case MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: this.stopSelf(); } - TermuxApiLogger.info("MicRecorderService onInfo() " + what); } protected static String getDefaultRecordingFilename() { @@ -168,7 +179,7 @@ protected static String getRecordingInfoJSONString() { info.put("outputFile", file.getAbsolutePath()); result = info.toString(2); } catch (JSONException e) { - TermuxApiLogger.error("infoHandler json error", e); + Logger.logStackTraceWithMessage(LOG_TAG, "infoHandler json error", e); } return result; } @@ -238,7 +249,7 @@ public RecorderCommandResult handle(Context context, Intent intent) { file = new File(filename); - TermuxApiLogger.info("MediaRecording file is: " + file.getAbsolutePath()); + Logger.logInfo(LOG_TAG, "MediaRecording file is: " + file.getAbsolutePath()); if (file.exists()) { result.error = String.format("File: %s already exists! Please specify a different filename", file.getName()); @@ -269,7 +280,7 @@ public RecorderCommandResult handle(Context context, Intent intent) { 1000)); } catch (IllegalStateException | IOException e) { - TermuxApiLogger.error("MediaRecorder error", e); + Logger.logStackTraceWithMessage(LOG_TAG, "MediaRecorder error", e); result.error = "Recording error: " + e.getMessage(); } } diff --git a/app/src/main/java/com/termux/api/apis/NfcAPI.java b/app/src/main/java/com/termux/api/apis/NfcAPI.java index d6081d7fc..a9df9ee39 100644 --- a/app/src/main/java/com/termux/api/apis/NfcAPI.java +++ b/app/src/main/java/com/termux/api/apis/NfcAPI.java @@ -12,28 +12,36 @@ import android.os.Bundle; import android.os.Parcelable; import android.util.JsonWriter; -import android.util.Log; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; public class NfcAPI { + private static final String LOG_TAG = "NfcAPI"; + public static void onReceive(final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + context.startActivity(new Intent(context, NfcActivity.class).putExtras(intent.getExtras()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } public static class NfcActivity extends AppCompatActivity { + private NfcAdapter adapter; static String socket_input; static String socket_output; String mode; String param; String value; + + private static final String LOG_TAG = "NfcActivity"; + //Check for NFC protected void errorNfc(final Context context, Intent intent, String error) { ResultReturner.returnData(context, intent, new ResultReturner.ResultJsonWriter() { @@ -53,6 +61,8 @@ public void writeJson(JsonWriter out) throws Exception { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + Logger.logDebug(LOG_TAG, "onCreate"); + super.onCreate(savedInstanceState); Intent intent = this.getIntent(); if (intent != null) { @@ -80,6 +90,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override protected void onResume() { + Logger.logVerbose(LOG_TAG, "onResume"); + super.onResume(); adapter = NfcAdapter.getDefaultAdapter(this); Intent intentNew = new Intent(this, NfcActivity.class).addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); @@ -93,16 +105,16 @@ protected void onResume() { @Override protected void onNewIntent(Intent intent) { + Logger.logDebug(LOG_TAG, "onNewIntent"); + intent.putExtra("socket_input", socket_input); intent.putExtra("socket_output", socket_output); if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { try { postResult(this, intent); - } - catch (Exception e) - { - Log.e("Termix-api.NfcAction",e.getMessage()); + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Error posting result" ,e); } finish(); } @@ -111,12 +123,16 @@ protected void onNewIntent(Intent intent) { @Override protected void onPause() { + Logger.logDebug(LOG_TAG, "onPause"); + adapter.disableForegroundDispatch(this); super.onPause(); } @Override protected void onDestroy() { + Logger.logDebug(LOG_TAG, "onDestroy"); + socket_input = null; socket_output = null; super.onDestroy(); @@ -126,16 +142,15 @@ protected void postResult(final Context context, Intent intent) { ResultReturner.returnData(context, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { - Log.e("NFC","postResult"); - try - { + Logger.logDebug(LOG_TAG, "postResult"); + try { switch (mode) { case "write": switch (param) { case "text": - Log.e("NFC","-->write"); + Logger.logVerbose(LOG_TAG, "Write start"); onReceiveNfcWrite(context, intent); - Log.e("NFC","<--write"); + Logger.logVerbose(LOG_TAG, "Write end"); break; default: onUnexpectedAction(out, "Wrong Params", "Should be text for TAG"); @@ -162,29 +177,30 @@ public void writeJson(JsonWriter out) throws Exception { onUnexpectedAction(out, "Wrong Params", "Should be correct mode value "); break; } - } - catch (Exception e){ + } catch (Exception e){ onUnexpectedAction(out, "exception", e.getMessage()); } } }); } + public void onReceiveNfcWrite( final Context context, Intent intent) throws Exception { - { - Log.e("NFC","---->onReceiveNfcWrite"); - NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); - Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - NdefRecord record = NdefRecord.createTextRecord("en", value); - NdefMessage msg = new NdefMessage(new NdefRecord[]{record}); - Ndef ndef = Ndef.get(tag); - ndef.connect(); - ndef.writeNdefMessage(msg); - ndef.close(); - } + Logger.logVerbose(LOG_TAG, "onReceiveNfcWrite"); + + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + NdefRecord record = NdefRecord.createTextRecord("en", value); + NdefMessage msg = new NdefMessage(new NdefRecord[]{record}); + Ndef ndef = Ndef.get(tag); + ndef.connect(); + ndef.writeNdefMessage(msg); + ndef.close(); } public void readNDEFTag(Intent intent, JsonWriter out) throws Exception { + Logger.logVerbose(LOG_TAG, "readNDEFTag"); + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); Parcelable[] msgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); @@ -228,6 +244,8 @@ public void readNDEFTag(Intent intent, JsonWriter out) throws Exception { } public void readFullNDEFTag(Intent intent, JsonWriter out) throws Exception { + Logger.logVerbose(LOG_TAG, "readFullNDEFTag"); + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); Ndef ndefTag = Ndef.get(tag); @@ -262,7 +280,7 @@ public void readFullNDEFTag(Intent intent, JsonWriter out) throws Exception { out.endArray(); } if (msgs.length == 1) { - Log.e("NFC", "-->> readFullNDEFTag - 06"); + Logger.logInfo(LOG_TAG, "-->> readFullNDEFTag - 06"); nmsgs[0] = (NdefMessage) msgs[0]; NdefRecord records[] = nmsgs[0].getRecords(); { diff --git a/app/src/main/java/com/termux/api/apis/NotificationAPI.java b/app/src/main/java/com/termux/api/apis/NotificationAPI.java index 60687a6bf..979590d0b 100644 --- a/app/src/main/java/com/termux/api/apis/NotificationAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationAPI.java @@ -22,7 +22,7 @@ import com.termux.api.R; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.File; import java.io.PrintWriter; @@ -35,6 +35,8 @@ public class NotificationAPI { + private static final String LOG_TAG = "NotificationAPI"; + public static final String TERMUX_SERVICE = "com.termux.app.TermuxService"; public static final String ACTION_EXECUTE = "com.termux.service_execute"; public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments"; @@ -48,6 +50,8 @@ public class NotificationAPI { * Show a notification. Driven by the termux-show-notification script. */ public static void onReceiveShowNotification(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveShowNotification"); + Pair pair = buildNotification(context, intent); NotificationCompat.Builder notification = pair.first; String notificationId = pair.second; @@ -78,6 +82,8 @@ public void writeResult(PrintWriter out) { } public static void onReceiveChannel(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveChannel"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { try { NotificationManager m = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); @@ -163,7 +169,7 @@ static Pair buildNotification(final Context try { ledColor = Integer.parseInt(lightsArgbExtra, 16) | 0xff000000; } catch (NumberFormatException e) { - TermuxApiLogger.error("Invalid LED color format! Ignoring!"); + Logger.logError(LOG_TAG, "Invalid LED color format! Ignoring!"); } } @@ -313,6 +319,8 @@ private static String getNotificationId(Intent intent) { } public static void onReceiveRemoveNotification(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveRemoveNotification"); + ResultReturner.noteDone(apiReceiver, intent); String notificationId = intent.getStringExtra("id"); if (notificationId != null) { @@ -375,6 +383,8 @@ static CharSequence shellEscape(CharSequence input) { public static void onReceiveReplyToNotification(TermuxApiReceiver termuxApiReceiver, Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveReplyToNotification"); + String replyKey = intent.getStringExtra("replyKey"); CharSequence reply = getMessageText(intent); @@ -383,7 +393,7 @@ public static void onReceiveReplyToNotification(TermuxApiReceiver termuxApiRecei try { createAction(context, action).send(); } catch (PendingIntent.CanceledException e) { - TermuxApiLogger.error("CanceledException when performing action: " + action); + Logger.logError(LOG_TAG, "CanceledException when performing action: " + action); } String notificationId = intent.getStringExtra("id"); diff --git a/app/src/main/java/com/termux/api/apis/NotificationListAPI.java b/app/src/main/java/com/termux/api/apis/NotificationListAPI.java index f111e541a..388e3520a 100644 --- a/app/src/main/java/com/termux/api/apis/NotificationListAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationListAPI.java @@ -13,11 +13,15 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; +import com.termux.shared.logger.Logger; public class NotificationListAPI { + private static final String LOG_TAG = "NotificationListAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); ResultReturner.returnData(apiReceiver, intent, new ResultJsonWriter() { @Override diff --git a/app/src/main/java/com/termux/api/apis/PhotoAPI.java b/app/src/main/java/com/termux/api/apis/PhotoAPI.java index 285115310..8b04c5796 100644 --- a/app/src/main/java/com/termux/api/apis/PhotoAPI.java +++ b/app/src/main/java/com/termux/api/apis/PhotoAPI.java @@ -22,7 +22,7 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.File; import java.io.FileOutputStream; @@ -37,7 +37,11 @@ public class PhotoAPI { + private static final String LOG_TAG = "PhotoAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + final String filePath = intent.getStringExtra("file"); final File outputFile = new File(filePath); final File outputDir = outputFile.getParentFile(); @@ -66,26 +70,26 @@ public void onOpened(final CameraDevice camera) { try { proceedWithOpenedCamera(context, manager, camera, outputFile, looper, stdout); } catch (Exception e) { - TermuxApiLogger.error("Exception in onOpened()", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Exception in onOpened()", e); closeCamera(camera, looper); } } @Override public void onDisconnected(CameraDevice camera) { - TermuxApiLogger.info("onDisconnected() from camera"); + Logger.logInfo(LOG_TAG, "onDisconnected() from camera"); } @Override public void onError(CameraDevice camera, int error) { - TermuxApiLogger.error("Failed opening camera: " + error); + Logger.logError(LOG_TAG, "Failed opening camera: " + error); closeCamera(camera, looper); } }, null); Looper.loop(); } catch (Exception e) { - TermuxApiLogger.error("Error getting camera", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error getting camera", e); } } @@ -126,7 +130,7 @@ public void run() { output.write(bytes); } catch (Exception e) { stdout.println("Error writing image: " + e.getMessage()); - TermuxApiLogger.error("Error writing image", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error writing image", e); } } finally { mImageReader.close(); @@ -155,10 +159,10 @@ public void onConfigured(final CameraCaptureSession session) { // continous preview-capture for 1/2 second session.setRepeatingRequest(previewReq.build(), null, null); - TermuxApiLogger.info("preview started"); + Logger.logInfo(LOG_TAG, "preview started"); Thread.sleep(500); session.stopRepeating(); - TermuxApiLogger.info("preview stoppend"); + Logger.logInfo(LOG_TAG, "preview stoppend"); final CaptureRequest.Builder jpegRequest = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); // Render to our image reader: @@ -170,7 +174,7 @@ public void onConfigured(final CameraCaptureSession session) { saveImage(camera, session, jpegRequest.build()); } catch (Exception e) { - TermuxApiLogger.error("onConfigured() error in preview", e); + Logger.logStackTraceWithMessage(LOG_TAG, "onConfigured() error in preview", e); mImageReader.close(); releaseSurfaces(outputSurfaces); closeCamera(camera, looper); @@ -179,7 +183,7 @@ public void onConfigured(final CameraCaptureSession session) { @Override public void onConfigureFailed(CameraCaptureSession session) { - TermuxApiLogger.error("onConfigureFailed() error in preview"); + Logger.logError(LOG_TAG, "onConfigureFailed() error in preview"); mImageReader.close(); releaseSurfaces(outputSurfaces); closeCamera(camera, looper); @@ -191,7 +195,7 @@ static void saveImage(final CameraDevice camera, CameraCaptureSession session, C session.capture(request, new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(CameraCaptureSession completedSession, CaptureRequest request, TotalCaptureResult result) { - TermuxApiLogger.info("onCaptureCompleted()"); + Logger.logInfo(LOG_TAG, "onCaptureCompleted()"); } }, null); } @@ -203,13 +207,13 @@ public void onCaptureCompleted(CameraCaptureSession completedSession, CaptureReq static int correctOrientation(final Context context, final CameraCharacteristics characteristics) { final Integer lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING); final boolean isFrontFacing = lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_FRONT; - TermuxApiLogger.info((isFrontFacing ? "Using" : "Not using") + " a front facing camera."); + Logger.logInfo(LOG_TAG, (isFrontFacing ? "Using" : "Not using") + " a front facing camera."); Integer sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); if (sensorOrientation != null) { - TermuxApiLogger.info(String.format("Sensor orientation: %s degrees", sensorOrientation)); + Logger.logInfo(LOG_TAG, String.format("Sensor orientation: %s degrees", sensorOrientation)); } else { - TermuxApiLogger.info("CameraCharacteristics didn't contain SENSOR_ORIENTATION. Assuming 0 degrees."); + Logger.logInfo(LOG_TAG, "CameraCharacteristics didn't contain SENSOR_ORIENTATION. Assuming 0 degrees."); sensorOrientation = 0; } @@ -230,11 +234,11 @@ static int correctOrientation(final Context context, final CameraCharacteristics deviceOrientation = 270; break; default: - TermuxApiLogger.info( + Logger.logInfo(LOG_TAG, String.format("Default display has unknown rotation %d. Assuming 0 degrees.", deviceRotation)); deviceOrientation = 0; } - TermuxApiLogger.info(String.format("Device orientation: %d degrees", deviceOrientation)); + Logger.logInfo(LOG_TAG, String.format("Device orientation: %d degrees", deviceOrientation)); int jpegOrientation; if (isFrontFacing) { @@ -244,7 +248,7 @@ static int correctOrientation(final Context context, final CameraCharacteristics } // Add an extra 360 because (-90 % 360) == -90 and Android won't accept a negative rotation. jpegOrientation = (jpegOrientation + 360) % 360; - TermuxApiLogger.info(String.format("Returning JPEG orientation of %d degrees", jpegOrientation)); + Logger.logInfo(LOG_TAG, String.format("Returning JPEG orientation of %d degrees", jpegOrientation)); return jpegOrientation; } @@ -252,14 +256,14 @@ static void releaseSurfaces(List outputSurfaces) { for (Surface outputSurface : outputSurfaces) { outputSurface.release(); } - TermuxApiLogger.info("surfaces released"); + Logger.logInfo(LOG_TAG, "surfaces released"); } static void closeCamera(CameraDevice camera, Looper looper) { try { camera.close(); } catch (RuntimeException e) { - TermuxApiLogger.info("Exception closing camera: " + e.getMessage()); + Logger.logInfo(LOG_TAG, "Exception closing camera: " + e.getMessage()); } if (looper != null) looper.quit(); } diff --git a/app/src/main/java/com/termux/api/apis/SAFAPI.java b/app/src/main/java/com/termux/api/apis/SAFAPI.java index 201bb1cb1..d66265f06 100644 --- a/app/src/main/java/com/termux/api/apis/SAFAPI.java +++ b/app/src/main/java/com/termux/api/apis/SAFAPI.java @@ -10,7 +10,6 @@ import android.os.FileUtils; import android.provider.DocumentsContract; import android.util.JsonWriter; -import android.webkit.MimeTypeMap; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -18,7 +17,8 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.data.IntentUtils; +import com.termux.shared.logger.Logger; import java.io.FileNotFoundException; import java.io.IOException; @@ -26,13 +26,20 @@ import java.io.OutputStream; import java.io.PrintWriter; -public class SAFAPI -{ +public class SAFAPI { + + private static final String LOG_TAG = "SAFAPI"; + public static class SAFActivity extends AppCompatActivity { + private boolean resultReturned = false; - + + private static final String LOG_TAG = "SAFActivity"; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + Logger.logDebug(LOG_TAG, "onCreate"); + super.onCreate(savedInstanceState); Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); i.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); @@ -41,6 +48,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override protected void onDestroy() { + Logger.logDebug(LOG_TAG, "onDestroy"); + super.onDestroy(); finishAndRemoveTask(); if (! resultReturned) { @@ -51,6 +60,8 @@ protected void onDestroy() { @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + Logger.logVerbose(LOG_TAG, "onActivityResult: requestCode: " + requestCode + ", resultCode: " + resultCode + ", data: " + IntentUtils.getIntentString(data)); + super.onActivityResult(requestCode, resultCode, data); if (data != null) { Uri uri = data.getData(); @@ -65,9 +76,11 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten } public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + String method = intent.getStringExtra("safmethod"); if (method == null) { - TermuxApiLogger.error("safmethod extra null"); + Logger.logError(LOG_TAG, "safmethod extra null"); return; } try { @@ -97,10 +110,10 @@ public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Int statURI(apiReceiver, context, intent); break; default: - TermuxApiLogger.error("Unrecognized safmethod: " + "'" + method + "'"); + Logger.logError(LOG_TAG, "Unrecognized safmethod: " + "'" + method + "'"); } } catch (Exception e) { - TermuxApiLogger.error("Error in SAFAPI", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error in SAFAPI", e); } } @@ -128,7 +141,7 @@ private static void manageDocumentTree(Context context, Intent intent) { private static void writeDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { String uri = intent.getStringExtra("uri"); if (uri == null) { - TermuxApiLogger.error("uri extra null"); + Logger.logError(LOG_TAG, "uri extra null"); return; } DocumentFile f = DocumentFile.fromSingleUri(context, Uri.parse(uri)); @@ -141,12 +154,12 @@ private static void writeDocument(TermuxApiReceiver apiReceiver, Context context private static void createDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { String treeURIString = intent.getStringExtra("treeuri"); if (treeURIString == null) { - TermuxApiLogger.error("treeuri extra null"); + Logger.logError(LOG_TAG, "treeuri extra null"); return; } String name = intent.getStringExtra("filename"); if (name == null) { - TermuxApiLogger.error("filename extra null"); + Logger.logError(LOG_TAG, "filename extra null"); return; } String mime = intent.getStringExtra("mimetype"); @@ -168,7 +181,7 @@ private static void createDocument(TermuxApiReceiver apiReceiver, Context contex private static void readDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { String uri = intent.getStringExtra("uri"); if (uri == null) { - TermuxApiLogger.error("uri extra null"); + Logger.logError(LOG_TAG, "uri extra null"); return; } DocumentFile f = DocumentFile.fromSingleUri(context, Uri.parse(uri)); @@ -181,7 +194,7 @@ private static void readDocument(TermuxApiReceiver apiReceiver, Context context, private static void listDirectory(TermuxApiReceiver apiReceiver, Context context, Intent intent) { String treeURIString = intent.getStringExtra("treeuri"); if (treeURIString == null) { - TermuxApiLogger.error("treeuri extra null"); + Logger.logError(LOG_TAG, "treeuri extra null"); return; } Uri treeURI = Uri.parse(treeURIString); @@ -210,7 +223,7 @@ public void writeJson(JsonWriter out) throws Exception { private static void statURI(TermuxApiReceiver apiReceiver, Context context, Intent intent) { String uriString = intent.getStringExtra("uri"); if (uriString == null) { - TermuxApiLogger.error("uri extra null"); + Logger.logError(LOG_TAG, "uri extra null"); return; } Uri docUri = treeUriToDocumentUri(Uri.parse(uriString)); @@ -227,7 +240,7 @@ public void writeJson(JsonWriter out) throws Exception { private static void removeDocument(TermuxApiReceiver apiReceiver, Context context, Intent intent) { String uri = intent.getStringExtra("uri"); if (uri == null) { - TermuxApiLogger.error("uri extra null"); + Logger.logError(LOG_TAG, "uri extra null"); return; } ResultReturner.returnData(apiReceiver, intent, out -> { diff --git a/app/src/main/java/com/termux/api/apis/SensorAPI.java b/app/src/main/java/com/termux/api/apis/SensorAPI.java index f2e18099a..7e85d0a69 100644 --- a/app/src/main/java/com/termux/api/apis/SensorAPI.java +++ b/app/src/main/java/com/termux/api/apis/SensorAPI.java @@ -12,7 +12,7 @@ import android.os.IBinder; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import org.json.JSONArray; import org.json.JSONException; @@ -30,10 +30,14 @@ */ public class SensorAPI { + private static final String LOG_TAG = "SensorAPI"; + /** * Starts our SensorReader service */ public static void onReceive(final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + Intent serviceIntent = new Intent(context, SensorReaderService.class); serviceIntent.setAction(intent.getAction()); serviceIntent.putExtras(intent.getExtras()); @@ -45,6 +49,7 @@ public static void onReceive(final Context context, final Intent intent) { * All sensor listening functionality exists in this background service */ public static class SensorReaderService extends Service { + // indentation for JSON output protected static final int INDENTATION = 2; @@ -55,8 +60,11 @@ public static class SensorReaderService extends Service { // prevent concurrent modifications w/ sensor readout protected static Semaphore semaphore; + private static final String LOG_TAG = "SensorReaderService"; public void onCreate() { + Logger.logDebug(LOG_TAG, "onCreate"); + super.onCreate(); sensorReadout = new JSONObject(); semaphore = new Semaphore(1); @@ -64,6 +72,8 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { + Logger.logDebug(LOG_TAG, "onStartCommand"); + String command = intent.getAction(); Context context = getApplicationContext(); SensorManager sensorManager = getSensorManager(context); @@ -87,9 +97,10 @@ protected static SensorManager getSensorManager(Context context) { @Override public void onDestroy() { + Logger.logDebug(LOG_TAG, "onDestroy"); + super.onDestroy(); cleanup(); - TermuxApiLogger.info("SensorAPI SensorReaderService onDestroy()"); } protected static void cleanup() { @@ -127,7 +138,7 @@ public void onSensorChanged(SensorEvent sensorEvent) { sensorReadout.put(sensorEvent.sensor.getName(), sensorInfo); semaphore.release(); } catch (JSONException e) { - TermuxApiLogger.error("onSensorChanged error", e); + Logger.logStackTraceWithMessage(LOG_TAG, "onSensorChanged error", e); } catch (InterruptedException e) { e.printStackTrace(); } @@ -194,7 +205,7 @@ private void postSensorCommandResult(final Context context, final Intent intent, output.put("sensors", sensorArray); result.message = output.toString(INDENTATION); } catch (JSONException e) { - TermuxApiLogger.error("listHandler JSON error", e); + Logger.logStackTraceWithMessage(LOG_TAG, "listHandler JSON error", e); } return result; }; @@ -212,7 +223,7 @@ public SensorCommandResult handle(SensorManager sensorManager, Context context, outputWriter = null; sensorManager.unregisterListener(sensorEventListener); result.message = "Sensor cleanup successful!"; - TermuxApiLogger.info("Cleanup()"); + Logger.logInfo(LOG_TAG, "Cleanup()"); } else { result.message = "Sensor cleanup unnecessary"; } @@ -272,7 +283,7 @@ protected static List getSensorsToListenTo(SensorManager sensorManager, sensorManager.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_UI); } sensorsToListenTo = availableSensors; - TermuxApiLogger.info("Listening to ALL sensors"); + Logger.logInfo(LOG_TAG, "Listening to ALL sensors"); } else { // try to find matching sensors that were sent in request @@ -313,15 +324,15 @@ protected static SensorOutputWriter createSensorOutputWriter(Intent intent) { outputWriter = new SensorOutputWriter(socketAddress); outputWriter.setOnErrorListener(e -> { outputWriter = null; - TermuxApiLogger.error("SensorOutputWriter error", e); + Logger.logStackTraceWithMessage(LOG_TAG, "SensorOutputWriter error", e); }); int delay = intent.getIntExtra("delay", SensorOutputWriter.DEFAULT_DELAY); - TermuxApiLogger.info("Delay set to: " + delay); + Logger.logInfo(LOG_TAG, "Delay set to: " + delay); outputWriter.setDelay(delay); int limit = intent.getIntExtra("limit", SensorOutputWriter.DEFAULT_LIMIT); - TermuxApiLogger.info("SensorOutput limit set to: " + limit); + Logger.logInfo(LOG_TAG, "SensorOutput limit set to: " + limit); outputWriter.setLimit(limit); return outputWriter; @@ -385,7 +396,7 @@ public void run() { try { Thread.sleep(this.delay); } catch (InterruptedException e) { - TermuxApiLogger.info("SensorOutputWriter interrupted: " + e.getMessage()); + Logger.logInfo(LOG_TAG, "SensorOutputWriter interrupted: " + e.getMessage()); } semaphore.acquire(); writer.write(sensorReadout.toString(INDENTATION) + "\n"); @@ -393,15 +404,15 @@ public void run() { semaphore.release(); if (++counter >= limit) { - TermuxApiLogger.info("SensorOutput limit reached! Performing cleanup"); + Logger.logInfo(LOG_TAG, "SensorOutput limit reached! Performing cleanup"); cleanup(); } } - TermuxApiLogger.info("SensorOutputWriter finished"); + Logger.logInfo(LOG_TAG, "SensorOutputWriter finished"); } } } catch (Exception e) { - TermuxApiLogger.error("SensorOutputWriter error", e); + Logger.logStackTraceWithMessage(LOG_TAG, "SensorOutputWriter error", e); if (errorListener != null) { errorListener.onError(e); diff --git a/app/src/main/java/com/termux/api/apis/ShareAPI.java b/app/src/main/java/com/termux/api/apis/ShareAPI.java index c2fc5fcb1..db405481a 100644 --- a/app/src/main/java/com/termux/api/apis/ShareAPI.java +++ b/app/src/main/java/com/termux/api/apis/ShareAPI.java @@ -14,15 +14,20 @@ import com.termux.api.R; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.PrintWriter; public class ShareAPI { + private static final String LOG_TAG = "ShareAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + final String fileExtra = intent.getStringExtra("file"); final String titleExtra = intent.getStringExtra("title"); final String contentTypeExtra = intent.getStringExtra("content-type"); @@ -44,7 +49,7 @@ public static void onReceive(TermuxApiReceiver apiReceiver, final Context contex intentAction = Intent.ACTION_VIEW; break; default: - TermuxApiLogger.error("Invalid action '" + actionExtra + "', using 'view'"); + Logger.logError(LOG_TAG, "Invalid action '" + actionExtra + "', using 'view'"); break; } } @@ -118,6 +123,8 @@ public void writeResult(PrintWriter out) { public static class ContentProvider extends android.content.ContentProvider { + private static final String LOG_TAG = "ContentProvider"; + @Override public boolean onCreate() { return true; @@ -184,7 +191,17 @@ public int update(Uri uri, ContentValues values, String selection, String[] sele @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { File file = new File(uri.getPath()); + + try { + String path = file.getCanonicalPath(); + String callingPackageName = getCallingPackage(); + Logger.logDebug(LOG_TAG, "Open file request received from " + callingPackageName + " for \"" + path + "\" with mode \"" + mode + "\""); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); } } + } diff --git a/app/src/main/java/com/termux/api/apis/SmsInboxAPI.java b/app/src/main/java/com/termux/api/apis/SmsInboxAPI.java index ae9a48fff..53b7838be 100644 --- a/app/src/main/java/com/termux/api/apis/SmsInboxAPI.java +++ b/app/src/main/java/com/termux/api/apis/SmsInboxAPI.java @@ -7,20 +7,17 @@ import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract.PhoneLookup; -import android.provider.Telephony; import android.provider.Telephony.Sms; import android.provider.Telephony.Sms.Conversations; import android.provider.Telephony.TextBasedSmsColumns; import android.util.JsonWriter; -import android.util.Log; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; +import com.termux.shared.logger.Logger; import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -37,7 +34,11 @@ public class SmsInboxAPI { private static final String[] DISPLAY_NAME_PROJECTION = {PhoneLookup.DISPLAY_NAME}; + private static final String LOG_TAG = "SmsInboxAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + final int offset = intent.getIntExtra("offset", 0); final int limit = intent.getIntExtra("limit", 10); final String number = intent.hasExtra("from") ? intent.getStringExtra("from"):""; @@ -192,5 +193,4 @@ private static Uri typeToContentURI(int type) { } } - } diff --git a/app/src/main/java/com/termux/api/apis/SmsSendAPI.java b/app/src/main/java/com/termux/api/apis/SmsSendAPI.java index 43457c433..82ecaa7c7 100644 --- a/app/src/main/java/com/termux/api/apis/SmsSendAPI.java +++ b/app/src/main/java/com/termux/api/apis/SmsSendAPI.java @@ -8,14 +8,18 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.PrintWriter; import java.util.ArrayList; public class SmsSendAPI { + private static final String LOG_TAG = "SmsSendAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.WithStringInput() { @Override public void writeResult(PrintWriter out) { @@ -31,7 +35,7 @@ public void writeResult(PrintWriter out) { } if (recipients == null || recipients.length == 0) { - TermuxApiLogger.error("No recipient given"); + Logger.logError(LOG_TAG, "No recipient given"); } else { final ArrayList messages = smsManager.divideMessage(inputString); for (String recipient : recipients) { @@ -49,7 +53,7 @@ static SmsManager getSmsManager(Context context, final Intent intent) { } else { SubscriptionManager sm = context.getSystemService(SubscriptionManager.class); if(sm == null) { - TermuxApiLogger.error("SubscriptionManager not supported"); + Logger.logError(LOG_TAG, "SubscriptionManager not supported"); return null; } for(SubscriptionInfo si: sm.getActiveSubscriptionInfoList()) { @@ -57,7 +61,7 @@ static SmsManager getSmsManager(Context context, final Intent intent) { return SmsManager.getSmsManagerForSubscriptionId(si.getSubscriptionId()); } } - TermuxApiLogger.error("Sim slot "+slot+" not found"); + Logger.logError(LOG_TAG, "Sim slot "+slot+" not found"); return null; } } diff --git a/app/src/main/java/com/termux/api/apis/SpeechToTextAPI.java b/app/src/main/java/com/termux/api/apis/SpeechToTextAPI.java index be0de6747..c33d7a31a 100644 --- a/app/src/main/java/com/termux/api/apis/SpeechToTextAPI.java +++ b/app/src/main/java/com/termux/api/apis/SpeechToTextAPI.java @@ -14,7 +14,8 @@ import android.speech.SpeechRecognizer; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.data.IntentUtils; +import com.termux.shared.logger.Logger; import java.io.PrintWriter; import java.util.List; @@ -22,6 +23,8 @@ public class SpeechToTextAPI { + private static final String LOG_TAG = "SpeechToTextAPI"; + public static class SpeechToTextService extends IntentService { private static final String STOP_ELEMENT = ""; @@ -37,8 +40,12 @@ public SpeechToTextService(String name) { protected SpeechRecognizer mSpeechRecognizer; final LinkedBlockingQueue queueu = new LinkedBlockingQueue<>(); + private static final String LOG_TAG = "SpeechToTextService"; + @Override public void onCreate() { + Logger.logDebug(LOG_TAG, "onCreate"); + super.onCreate(); final Context context = this; @@ -53,7 +60,7 @@ public void onRmsChanged(float rmsdB) { @Override public void onResults(Bundle results) { List recognitions = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); - TermuxApiLogger.error("RecognitionListener#onResults(" + recognitions + ")"); + Logger.logError(LOG_TAG, "RecognitionListener#onResults(" + recognitions + ")"); queueu.addAll(recognitions); } @@ -66,7 +73,7 @@ public void onReadyForSpeech(Bundle params) { public void onPartialResults(Bundle partialResults) { // Do nothing. List strings = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); - TermuxApiLogger.error("RecognitionListener#onPartialResults(" + strings + ")"); + Logger.logError(LOG_TAG, "RecognitionListener#onPartialResults(" + strings + ")"); queueu.addAll(strings); } @@ -94,13 +101,13 @@ public void onError(int error) { default: description = Integer.toString(error); } - TermuxApiLogger.error("RecognitionListener#onError(" + description + ")"); + Logger.logError(LOG_TAG, "RecognitionListener#onError(" + description + ")"); queueu.add(STOP_ELEMENT); } @Override public void onEndOfSpeech() { - TermuxApiLogger.error("RecognitionListener#onEndOfSpeech()"); + Logger.logError(LOG_TAG, "RecognitionListener#onEndOfSpeech()"); queueu.add(STOP_ELEMENT); } @@ -145,14 +152,16 @@ public void onBeginningOfSpeech() { @Override public void onDestroy() { + Logger.logDebug(LOG_TAG, "onDestroy"); + super.onDestroy(); - TermuxApiLogger.error("onDestroy"); mSpeechRecognizer.destroy(); } @Override protected void onHandleIntent(final Intent intent) { - TermuxApiLogger.error("onHandleIntent"); + Logger.logDebug(LOG_TAG, "onHandleIntent:\n" + IntentUtils.getIntentString(intent)); + ResultReturner.returnData(this, intent, new ResultReturner.WithInput() { @Override public void writeResult(PrintWriter out) throws Exception { @@ -171,6 +180,8 @@ public void writeResult(PrintWriter out) throws Exception { } public static void onReceive(final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + context.startService(new Intent(context, SpeechToTextService.class).putExtras(intent.getExtras())); } diff --git a/app/src/main/java/com/termux/api/apis/StorageGetAPI.java b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java index f952cdc3f..01ba5a3e4 100644 --- a/app/src/main/java/com/termux/api/apis/StorageGetAPI.java +++ b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java @@ -4,10 +4,14 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Bundle; + +import androidx.annotation.Nullable; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.data.IntentUtils; +import com.termux.shared.logger.Logger; import java.io.File; import java.io.FileOutputStream; @@ -19,7 +23,11 @@ public class StorageGetAPI { private static final String FILE_EXTRA = "com.termux.api.storage.file"; + private static final String LOG_TAG = "StorageGetAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + ResultReturner.returnData(apiReceiver, intent, out -> { final String fileExtra = intent.getStringExtra("file"); if (fileExtra == null || !new File(fileExtra).getParentFile().canWrite()) { @@ -38,8 +46,19 @@ public static class StorageActivity extends Activity { private String outputFile; + private static final String LOG_TAG = "StorageActivity"; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + Logger.logDebug(LOG_TAG, "onCreate"); + + super.onCreate(savedInstanceState); + } + @Override public void onResume() { + Logger.logVerbose(LOG_TAG, "onResume"); + super.onResume(); outputFile = getIntent().getStringExtra(FILE_EXTRA); @@ -57,6 +76,8 @@ public void onResume() { @Override protected void onActivityResult(int requestCode, int resultCode, Intent resultData) { + Logger.logVerbose(LOG_TAG, "onActivityResult: requestCode: " + requestCode + ", resultCode: " + resultCode + ", data: " + IntentUtils.getIntentString(resultData)); + super.onActivityResult(requestCode, resultCode, resultData); if (resultCode == RESULT_OK) { Uri data = resultData.getData(); @@ -75,7 +96,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent resultDa } } } catch (IOException e) { - TermuxApiLogger.error("Error copying " + data + " to " + outputFile); + Logger.logStackTraceWithMessage(LOG_TAG, "Error copying " + data + " to " + outputFile, e); } } finish(); diff --git a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java index 5747a7f1b..acac47078 100644 --- a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java +++ b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java @@ -16,10 +16,10 @@ import android.telephony.CellSignalStrengthNr; import android.telephony.TelephonyManager; import android.util.JsonWriter; -import android.util.Log; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; import java.io.IOException; @@ -30,6 +30,8 @@ */ public class TelephonyAPI { + private static final String LOG_TAG = "TelephonyAPI"; + private static void writeIfKnown(JsonWriter out, String name, int value) throws IOException { if (value != Integer.MAX_VALUE) out.name(name).value(value); } @@ -47,6 +49,8 @@ private static void writeIfKnown(JsonWriter out, String name, int value[]) throw } public static void onReceiveTelephonyCellInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveTelephonyCellInfo"); + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { @@ -174,6 +178,8 @@ public void writeJson(JsonWriter out) throws Exception { public static void onReceiveTelephonyDeviceInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveTelephonyDeviceInfo"); + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @SuppressLint("HardwareIds") @Override @@ -379,9 +385,11 @@ public void writeJson(JsonWriter out) throws Exception { } public static void onReceiveTelephonyCall(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveTelephonyCall"); + String numberExtra = intent.getStringExtra("number"); if (numberExtra == null) { - Log.e("termux-api", "No 'number extra"); + Logger.logError(LOG_TAG, "No 'number' extra"); ResultReturner.noteDone(apiReceiver, intent); } @@ -397,7 +405,7 @@ public static void onReceiveTelephonyCall(TermuxApiReceiver apiReceiver, final C try { context.startActivity(callIntent); } catch (SecurityException e) { - Log.e("termux-api", "Exception in phone call", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Exception in phone call", e); } ResultReturner.noteDone(apiReceiver, intent); diff --git a/app/src/main/java/com/termux/api/apis/TextToSpeechAPI.java b/app/src/main/java/com/termux/api/apis/TextToSpeechAPI.java index 587f8f650..2dae2dd1c 100644 --- a/app/src/main/java/com/termux/api/apis/TextToSpeechAPI.java +++ b/app/src/main/java/com/termux/api/apis/TextToSpeechAPI.java @@ -12,7 +12,8 @@ import android.util.JsonWriter; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.data.IntentUtils; +import com.termux.shared.logger.Logger; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -24,7 +25,11 @@ public class TextToSpeechAPI { + private static final String LOG_TAG = "TextToSpeechAPI"; + public static void onReceive(final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + context.startService(new Intent(context, TextToSpeechService.class).putExtras(intent.getExtras())); } @@ -32,12 +37,23 @@ public static class TextToSpeechService extends IntentService { TextToSpeech mTts; final CountDownLatch mTtsLatch = new CountDownLatch(1); + private static final String LOG_TAG = "TextToSpeechService"; + public TextToSpeechService() { super(TextToSpeechService.class.getName()); } + @Override + public void onCreate() { + Logger.logDebug(LOG_TAG, "onCreate"); + + super.onCreate(); + } + @Override public void onDestroy() { + Logger.logDebug(LOG_TAG, "onDestroy"); + if (mTts != null) mTts.shutdown(); super.onDestroy(); @@ -45,6 +61,8 @@ public void onDestroy() { @Override protected void onHandleIntent(final Intent intent) { + Logger.logDebug(LOG_TAG, "onHandleIntent:\n" + IntentUtils.getIntentString(intent)); + final String speechLanguage = intent.getStringExtra("language"); final String speechRegion = intent.getStringExtra("region"); final String speechVariant = intent.getStringExtra("variant"); @@ -83,7 +101,7 @@ protected void onHandleIntent(final Intent intent) { if (status == TextToSpeech.SUCCESS) { mTtsLatch.countDown(); } else { - TermuxApiLogger.error("Failed tts initialization: status=" + status); + Logger.logError(LOG_TAG, "Failed tts initialization: status=" + status); stopSelf(); } }, speechEngine); @@ -95,11 +113,11 @@ public void writeResult(PrintWriter out) { try { try { if (!mTtsLatch.await(10, TimeUnit.SECONDS)) { - TermuxApiLogger.error("Timeout waiting for TTS initialization"); + Logger.logError(LOG_TAG, "Timeout waiting for TTS initialization"); return; } } catch (InterruptedException e) { - TermuxApiLogger.error("Interrupted awaiting TTS initialization"); + Logger.logError(LOG_TAG, "Interrupted awaiting TTS initialization"); return; } @@ -131,7 +149,7 @@ public void onStart(String utteranceId) { @Override public void onError(String utteranceId) { - TermuxApiLogger.error("UtteranceProgressListener.onError() called"); + Logger.logError(LOG_TAG, "UtteranceProgressListener.onError() called"); synchronized (ttsDoneUtterancesCount) { ttsDoneUtterancesCount.incrementAndGet(); ttsDoneUtterancesCount.notify(); @@ -150,7 +168,7 @@ public void onDone(String utteranceId) { if (speechLanguage != null) { int setLanguageResult = mTts.setLanguage(getLocale(speechLanguage, speechRegion, speechVariant)); if (setLanguageResult != TextToSpeech.LANG_AVAILABLE) { - TermuxApiLogger.error("tts.setLanguage('" + speechLanguage + "') returned " + setLanguageResult); + Logger.logError(LOG_TAG, "tts.setLanguage('" + speechLanguage + "') returned " + setLanguageResult); } } @@ -180,7 +198,7 @@ public void onDone(String utteranceId) { } } } catch (Exception e) { - TermuxApiLogger.error("TTS error", e); + Logger.logStackTraceWithMessage(LOG_TAG, "TTS error", e); } } }); diff --git a/app/src/main/java/com/termux/api/apis/ToastAPI.java b/app/src/main/java/com/termux/api/apis/ToastAPI.java index 1bb5eaeed..96ea2ea03 100644 --- a/app/src/main/java/com/termux/api/apis/ToastAPI.java +++ b/app/src/main/java/com/termux/api/apis/ToastAPI.java @@ -11,13 +11,17 @@ import android.widget.Toast; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.PrintWriter; public class ToastAPI { + private static final String LOG_TAG = "ToastAPI"; + public static void onReceive(final Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + final int durationExtra = intent.getBooleanExtra("short", false) ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG; final int backgroundColor = getColorExtra(intent, "background", Color.GRAY); final int textColor = getColorExtra(intent, "text_color", Color.WHITE); @@ -54,7 +58,7 @@ protected static int getColorExtra(Intent intent, String extra, int defaultColor try { color = Color.parseColor(colorExtra); } catch (IllegalArgumentException e) { - TermuxApiLogger.error(String.format("Failed to parse color '%s' for '%s'", colorExtra, extra)); + Logger.logError(LOG_TAG, String.format("Failed to parse color '%s' for '%s'", colorExtra, extra)); } } return color; diff --git a/app/src/main/java/com/termux/api/apis/TorchAPI.java b/app/src/main/java/com/termux/api/apis/TorchAPI.java index 89aa519a0..1e0867c50 100644 --- a/app/src/main/java/com/termux/api/apis/TorchAPI.java +++ b/app/src/main/java/com/termux/api/apis/TorchAPI.java @@ -1,23 +1,25 @@ package com.termux.api.apis; -import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.hardware.Camera; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; -import android.os.Build; import android.widget.Toast; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; public class TorchAPI { private static Camera legacyCamera; + private static final String LOG_TAG = "TorchAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + boolean enabled = intent.getBooleanExtra("enabled", false); toggleTorch(context, enabled); @@ -35,12 +37,12 @@ private static void toggleTorch(Context context, boolean enabled) { Toast.makeText(context, "Torch unavailable on your device", Toast.LENGTH_LONG).show(); } } catch (CameraAccessException e) { - TermuxApiLogger.error("Error toggling torch", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error toggling torch", e); } } private static void legacyToggleTorch(boolean enabled) { - TermuxApiLogger.info("Using legacy camera api to toggle torch"); + Logger.logInfo(LOG_TAG, "Using legacy camera api to toggle torch"); if (legacyCamera == null) { legacyCamera = Camera.open(); diff --git a/app/src/main/java/com/termux/api/apis/UsbAPI.java b/app/src/main/java/com/termux/api/apis/UsbAPI.java index ad82428d8..74ca0fdaf 100644 --- a/app/src/main/java/com/termux/api/apis/UsbAPI.java +++ b/app/src/main/java/com/termux/api/apis/UsbAPI.java @@ -14,6 +14,7 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; import java.io.IOException; import java.io.PrintWriter; @@ -26,7 +27,11 @@ public class UsbAPI { private static SparseArray openDevices = new SparseArray<>(); + private static final String LOG_TAG = "UsbAPI"; + public static void onReceive(final TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + UsbDevice device; String action = intent.getAction(); if (action == null) { diff --git a/app/src/main/java/com/termux/api/apis/VibrateAPI.java b/app/src/main/java/com/termux/api/apis/VibrateAPI.java index 54c5a0100..4892b0e19 100644 --- a/app/src/main/java/com/termux/api/apis/VibrateAPI.java +++ b/app/src/main/java/com/termux/api/apis/VibrateAPI.java @@ -7,10 +7,15 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; public class VibrateAPI { + private static final String LOG_TAG = "VibrateAPI"; + public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); int milliseconds = intent.getIntExtra("duration_ms", 1000); boolean force = intent.getBooleanExtra("force", false); diff --git a/app/src/main/java/com/termux/api/apis/VolumeAPI.java b/app/src/main/java/com/termux/api/apis/VolumeAPI.java index 4eae33602..6febb9d8f 100644 --- a/app/src/main/java/com/termux/api/apis/VolumeAPI.java +++ b/app/src/main/java/com/termux/api/apis/VolumeAPI.java @@ -8,6 +8,7 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; import java.io.IOException; @@ -25,8 +26,11 @@ public class VolumeAPI { streamMap.append(AudioManager.STREAM_VOICE_CALL, "call"); } + private static final String LOG_TAG = "VolumeAPI"; public static void onReceive(final TermuxApiReceiver receiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + final AudioManager audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); String action = intent.getAction(); diff --git a/app/src/main/java/com/termux/api/apis/WallpaperAPI.java b/app/src/main/java/com/termux/api/apis/WallpaperAPI.java index 8a885ab23..d21fa063c 100644 --- a/app/src/main/java/com/termux/api/apis/WallpaperAPI.java +++ b/app/src/main/java/com/termux/api/apis/WallpaperAPI.java @@ -10,7 +10,7 @@ import android.os.IBinder; import com.termux.api.util.ResultReturner; -import com.termux.api.util.TermuxApiLogger; +import com.termux.shared.logger.Logger; import java.io.IOException; import java.io.InputStream; @@ -24,7 +24,11 @@ public class WallpaperAPI { + private static final String LOG_TAG = "WallpaperAPI"; + public static void onReceive(final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceive"); + Intent wallpaperService = new Intent(context, WallpaperService.class); wallpaperService.putExtras(intent.getExtras()); context.startService(wallpaperService); @@ -38,8 +42,11 @@ public static void onReceive(final Context context, final Intent intent) { public static class WallpaperService extends Service { protected static final int DOWNLOAD_TIMEOUT = 30; + private static final String LOG_TAG = "WallpaperService"; public int onStartCommand(Intent intent, int flags, int startId) { + Logger.logDebug(LOG_TAG, "onStartCommand"); + if (intent.hasExtra("file")) { getWallpaperFromFile(intent); } else if (intent.hasExtra("url")) { @@ -72,7 +79,7 @@ protected void getWallpaperFromUrl(final Intent intent) { try { result = wallpaperDownload.get(DOWNLOAD_TIMEOUT, TimeUnit.SECONDS); } catch (InterruptedException e) { - TermuxApiLogger.info("Wallpaper download interrupted"); + Logger.logInfo(LOG_TAG, "Wallpaper download interrupted"); } catch (ExecutionException e) { result.error = "Unknown host!"; } catch (TimeoutException e) { diff --git a/app/src/main/java/com/termux/api/apis/WifiAPI.java b/app/src/main/java/com/termux/api/apis/WifiAPI.java index f96b8ad0b..1e9d1eae5 100644 --- a/app/src/main/java/com/termux/api/apis/WifiAPI.java +++ b/app/src/main/java/com/termux/api/apis/WifiAPI.java @@ -7,19 +7,23 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.os.Build; import android.text.TextUtils; import android.text.format.Formatter; import android.util.JsonWriter; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.logger.Logger; import java.util.List; public class WifiAPI { + private static final String LOG_TAG = "WifiAPI"; + public static void onReceiveWifiConnectionInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveWifiConnectionInfo"); + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @SuppressLint("HardwareIds") @Override @@ -53,6 +57,8 @@ static boolean isLocationEnabled(Context context) { } public static void onReceiveWifiScanInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveWifiScanInfo"); + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) throws Exception { @@ -114,6 +120,8 @@ public void writeJson(JsonWriter out) throws Exception { } public static void onReceiveWifiEnable(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { + Logger.logDebug(LOG_TAG, "onReceiveWifiEnable"); + ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @Override public void writeJson(JsonWriter out) { diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java index 921f68a16..5766717d9 100644 --- a/app/src/main/java/com/termux/api/util/ResultReturner.java +++ b/app/src/main/java/com/termux/api/util/ResultReturner.java @@ -10,6 +10,8 @@ import android.os.ParcelFileDescriptor; import android.util.JsonWriter; +import com.termux.shared.logger.Logger; + import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.InputStream; @@ -19,6 +21,8 @@ public abstract class ResultReturner { + private static final String LOG_TAG = "ResultReturner"; + /** * An extra intent parameter which specifies a linux abstract namespace socket address where output from the API * call should be written. @@ -179,7 +183,7 @@ public static void returnData(Object context, final Intent intent, final ResultW activity.setResult(0); } } catch (Exception e) { - TermuxApiLogger.error("Error in ResultReturner", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Error in ResultReturner", e); if (asyncResult != null) { asyncResult.setResultCode(1); } else if (activity != null) { diff --git a/app/src/main/java/com/termux/api/util/TermuxApiLogger.java b/app/src/main/java/com/termux/api/util/TermuxApiLogger.java deleted file mode 100644 index bfd038bc6..000000000 --- a/app/src/main/java/com/termux/api/util/TermuxApiLogger.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.termux.api.util; - -import android.util.Log; - -public class TermuxApiLogger { - - private static final String TAG = "termux-api"; - - public static void info(String message) { - Log.i(TAG, message); - } - - public static void error(String message) { - Log.e(TAG, message); - } - - public static void error(String message, Exception exception) { - Log.e(TAG, message, exception); - } - -} From feb568c282dc5e017a6e90b703237165c1b33ad2 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 13 Mar 2022 01:14:17 +0500 Subject: [PATCH 024/142] Fixed: Add missing return statement after error in onReceiveTelephonyCall() --- app/src/main/java/com/termux/api/apis/TelephonyAPI.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java index acac47078..e1c914549 100644 --- a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java +++ b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java @@ -391,6 +391,7 @@ public static void onReceiveTelephonyCall(TermuxApiReceiver apiReceiver, final C if (numberExtra == null) { Logger.logError(LOG_TAG, "No 'number' extra"); ResultReturner.noteDone(apiReceiver, intent); + return; } if(numberExtra.contains("#")) From 292c3091f740999343fa9e2e57a1cdaa49490566 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 13 Mar 2022 01:39:58 +0500 Subject: [PATCH 025/142] Changed: Sort permissions and features in AndroidManifest --- app/src/main/AndroidManifest.xml | 57 +++++++++++++++++--------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e9ee6952a..9d0376c18 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,36 +4,38 @@ package="com.termux.api" android:sharedUserId="${TERMUX_PACKAGE_NAME}"> - - - - - - - - - + + - - - - - - - - - + + + - - - + + + + + + + + + + + + + + + + + + @@ -44,12 +46,11 @@ + - - + - - From 2968854fa7a73f40c86693f7883626b52f7a59bc Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 13 Mar 2022 01:43:25 +0500 Subject: [PATCH 026/142] Changed: Protect ShareAPI.ContentProvider write acess with permissoion in case it was allowed in future without setting permission --- app/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9d0376c18..a1e424b71 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,7 +33,7 @@ - + @@ -109,7 +109,7 @@ From 5c71e2df36718157a548ca272a36deef90c487e3 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 13 Mar 2022 01:50:57 +0500 Subject: [PATCH 027/142] Changed: Use night-mode instead of use-black-ui termux.properties property for setting DialogActivity theme as per termux/termux-app@6631599f --- .../java/com/termux/api/apis/DialogAPI.java | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java index 512a02f16..9d8d2f044 100644 --- a/app/src/main/java/com/termux/api/apis/DialogAPI.java +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -49,6 +49,9 @@ import com.termux.api.util.ResultReturner; import com.termux.api.activities.TermuxApiPermissionActivity; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.theme.TermuxThemeUtils; +import com.termux.shared.theme.NightMode; +import com.termux.shared.theme.ThemeUtils; import java.io.File; import java.io.FileInputStream; @@ -83,31 +86,6 @@ public static class DialogActivity extends AppCompatActivity { private boolean resultReturned = false; - protected boolean getBlackUI() { - File propsFile = new File(TERMUX_PROPERTIES_PRIMARY_FILE_PATH); - - if (!propsFile.exists()) - propsFile = new File(TERMUX_PROPERTIES_SECONDARY_FILE_PATH); - - boolean mUseBlackUi = false; - - if (propsFile.exists()) { - Properties props = new Properties(); - try { - if (propsFile.isFile() && propsFile.canRead()) { - try (FileInputStream in = new FileInputStream(propsFile)) { - props.load(new InputStreamReader(in, StandardCharsets.UTF_8)); - } - } - mUseBlackUi = props.getProperty("use-black-ui").equals("true"); - } catch (Exception e) { - Logger.logStackTraceWithMessage(LOG_TAG, "Error loading props", e); - } - } - - return mUseBlackUi; - } - @Override protected void onCreate(Bundle savedInstanceState) { Logger.logDebug(LOG_TAG, "onCreate"); @@ -119,7 +97,10 @@ protected void onCreate(Bundle savedInstanceState) { String methodType = intent.hasExtra("input_method") ? intent.getStringExtra("input_method") : ""; - if (getBlackUI()) + // Set NightMode.APP_NIGHT_MODE + TermuxThemeUtils.setAppNightMode(context); + boolean shouldEnableDarkTheme = ThemeUtils.shouldEnableDarkTheme(this, NightMode.getAppNightMode().getName()); + if (shouldEnableDarkTheme) this.setTheme(R.style.DialogTheme_Dark); InputMethod method = InputMethodFactory.get(methodType, this); From 416c525e85517ad29e43ac5c067d15993c3d13d4 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 15:53:46 +0500 Subject: [PATCH 028/142] Added: Add missing attributes for dialog_textarea_input --- app/src/main/res/layout/dialog_textarea_input.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/dialog_textarea_input.xml b/app/src/main/res/layout/dialog_textarea_input.xml index 2d1331c0c..88446c311 100644 --- a/app/src/main/res/layout/dialog_textarea_input.xml +++ b/app/src/main/res/layout/dialog_textarea_input.xml @@ -1,5 +1,6 @@ + android:scrollbars="vertical" + android:importantForAutofill="no" + tools:ignore="LabelFor" /> Date: Mon, 14 Mar 2022 16:30:19 +0500 Subject: [PATCH 029/142] Fixed: Fix missing API level checks for method calls --- .../java/com/termux/api/apis/TelephonyAPI.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java index e1c914549..224a2a193 100644 --- a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java +++ b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java @@ -98,9 +98,15 @@ public void writeJson(JsonWriter out) throws Exception { writeIfKnown(out, "mcc", lteInfo.getCellIdentity().getMcc()); writeIfKnown(out, "mnc", lteInfo.getCellIdentity().getMnc()); - writeIfKnown(out, "rsrp", lteInfo.getCellSignalStrength().getRsrp()); - writeIfKnown(out, "rsrq", lteInfo.getCellSignalStrength().getRsrq()); - writeIfKnown(out, "rssi", lteInfo.getCellSignalStrength().getRssi()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + writeIfKnown(out, "rsrp", lteInfo.getCellSignalStrength().getRsrp()); + writeIfKnown(out, "rsrq", lteInfo.getCellSignalStrength().getRsrq()); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + writeIfKnown(out, "rssi", lteInfo.getCellSignalStrength().getRssi()); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { writeIfKnown(out, "bands", lteInfo.getCellIdentity().getBands()); } @@ -176,7 +182,6 @@ public void writeJson(JsonWriter out) throws Exception { }); } - public static void onReceiveTelephonyDeviceInfo(TermuxApiReceiver apiReceiver, final Context context, final Intent intent) { Logger.logDebug(LOG_TAG, "onReceiveTelephonyDeviceInfo"); @@ -242,7 +247,9 @@ public void writeJson(JsonWriter out) throws Exception { String device_id = null; try { - device_id = phoneType == TelephonyManager.PHONE_TYPE_GSM ? manager.getImei() : manager.getMeid(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + device_id = phoneType == TelephonyManager.PHONE_TYPE_GSM ? manager.getImei() : manager.getMeid(); + } } catch (SecurityException e) { // Failed to obtain device id. // Android 10+. From 28fce53ce1f2e83a4132823b567cc699de7b7348 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 16:32:56 +0500 Subject: [PATCH 030/142] Added: Add RequiresPermission annotations to methods --- app/src/main/java/com/termux/api/apis/LocationAPI.java | 4 ++++ app/src/main/java/com/termux/api/apis/SmsSendAPI.java | 5 +++++ app/src/main/java/com/termux/api/apis/TelephonyAPI.java | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/app/src/main/java/com/termux/api/apis/LocationAPI.java b/app/src/main/java/com/termux/api/apis/LocationAPI.java index cf70ea45c..4924d4c90 100644 --- a/app/src/main/java/com/termux/api/apis/LocationAPI.java +++ b/app/src/main/java/com/termux/api/apis/LocationAPI.java @@ -1,5 +1,6 @@ package com.termux.api.apis; +import android.Manifest; import android.content.Context; import android.content.Intent; import android.location.Location; @@ -11,6 +12,8 @@ import android.os.SystemClock; import android.util.JsonWriter; +import androidx.annotation.RequiresPermission; + import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.api.util.ResultReturner.ResultJsonWriter; @@ -30,6 +33,7 @@ public static void onReceive(TermuxApiReceiver apiReceiver, final Context contex Logger.logDebug(LOG_TAG, "onReceive"); ResultReturner.returnData(apiReceiver, intent, new ResultJsonWriter() { + @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) @Override public void writeJson(final JsonWriter out) throws Exception { LocationManager manager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); diff --git a/app/src/main/java/com/termux/api/apis/SmsSendAPI.java b/app/src/main/java/com/termux/api/apis/SmsSendAPI.java index 82ecaa7c7..c7d34862d 100644 --- a/app/src/main/java/com/termux/api/apis/SmsSendAPI.java +++ b/app/src/main/java/com/termux/api/apis/SmsSendAPI.java @@ -1,11 +1,14 @@ package com.termux.api.apis; +import android.Manifest; import android.content.Context; import android.content.Intent; import android.telephony.SmsManager; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionInfo; +import androidx.annotation.RequiresPermission; + import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.shared.logger.Logger; @@ -21,6 +24,7 @@ public static void onReceive(TermuxApiReceiver apiReceiver, Context context, fin Logger.logDebug(LOG_TAG, "onReceive"); ResultReturner.returnData(apiReceiver, intent, new ResultReturner.WithStringInput() { + @RequiresPermission(allOf = { Manifest.permission.READ_PHONE_STATE, Manifest.permission.SEND_SMS }) @Override public void writeResult(PrintWriter out) { final SmsManager smsManager = getSmsManager(context,intent); @@ -46,6 +50,7 @@ public void writeResult(PrintWriter out) { }); } + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) static SmsManager getSmsManager(Context context, final Intent intent) { int slot = intent.getIntExtra("slot", -1); if(slot == -1) { diff --git a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java index 224a2a193..7685b8822 100644 --- a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java +++ b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java @@ -1,5 +1,6 @@ package com.termux.api.apis; +import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; @@ -17,6 +18,8 @@ import android.telephony.TelephonyManager; import android.util.JsonWriter; +import androidx.annotation.RequiresPermission; + import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.shared.logger.Logger; @@ -186,6 +189,7 @@ public static void onReceiveTelephonyDeviceInfo(TermuxApiReceiver apiReceiver, f Logger.logDebug(LOG_TAG, "onReceiveTelephonyDeviceInfo"); ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) @SuppressLint("HardwareIds") @Override public void writeJson(JsonWriter out) throws Exception { From 8d6c6ce14ba510cbf2c86b1cbdcc23c90bd7ea21 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 16:35:31 +0500 Subject: [PATCH 031/142] Added: Declare READ_PRIVILEGED_PHONE_STATE permission for TelePhony API IMEI and MEID --- app/src/main/AndroidManifest.xml | 1 + app/src/main/java/com/termux/api/apis/TelephonyAPI.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a1e424b71..aaf3d4aaf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ + diff --git a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java index 7685b8822..8a0e7bb27 100644 --- a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java +++ b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java @@ -256,7 +256,8 @@ public void writeJson(JsonWriter out) throws Exception { } } catch (SecurityException e) { // Failed to obtain device id. - // Android 10+. + // Android 10+ requires READ_PRIVILEGED_PHONE_STATE + // https://source.android.com/devices/tech/config/device-identifiers } out.name("device_id").value(device_id); From d606d131f5e2407d41e0d427a048c51353d211e6 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 16:39:54 +0500 Subject: [PATCH 032/142] Changed: Suppress build.gradle validateVersionName() warnings --- app/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 1edb5c975..deb98cb5d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,7 +80,8 @@ task versionName { } } -def validateVersionName(String versionName) { +@SuppressWarnings("UnnecessaryQualifiedReference") +static def validateVersionName(String versionName) { // https://semver.org/spec/v2.0.0.html#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string // ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ if (!java.util.regex.Pattern.matches("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?\$", versionName)) From bf6d834acc51b63519468979e20c88c20a660bbb Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 16:40:29 +0500 Subject: [PATCH 033/142] Fixed: Check for READ_PHONE_STATE for SmsSend API --- app/src/main/java/com/termux/api/TermuxApiReceiver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index a96f08b56..8c5392d38 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -196,7 +196,7 @@ private void doWork(Context context, Intent intent) { } break; case "SmsSend": - if (TermuxApiPermissionActivity.checkAndRequestPermissions(context, intent, Manifest.permission.SEND_SMS)) { + if (TermuxApiPermissionActivity.checkAndRequestPermissions(context, intent, Manifest.permission.READ_PHONE_STATE, Manifest.permission.SEND_SMS)) { SmsSendAPI.onReceive(this, context, intent); } break; From 58c272ba659478d00fec5000a8393a70f80150ea Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 16:44:58 +0500 Subject: [PATCH 034/142] Fixed: Fallback from cellular to unmetered on Android < 9 for JobSchedulerAPI network requirement --- app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java index bcad38943..ea1874248 100644 --- a/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java +++ b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java @@ -70,7 +70,7 @@ public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Int final boolean cancelAll = intent.getBooleanExtra("cancel_all", false); final int periodicMillis = intent.getIntExtra("period_ms", 0); - final String networkType = intent.getStringExtra("network"); + String networkType = intent.getStringExtra("network"); final boolean batteryNotLow = intent.getBooleanExtra("battery_not_low", true); final boolean charging = intent.getBooleanExtra("charging", false); final boolean persisted = intent.getBooleanExtra("persisted", false); @@ -87,7 +87,10 @@ public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Int networkTypeCode = JobInfo.NETWORK_TYPE_UNMETERED; break; case "cellular": - networkTypeCode = JobInfo.NETWORK_TYPE_CELLULAR; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + networkTypeCode = JobInfo.NETWORK_TYPE_CELLULAR; + else + networkTypeCode = JobInfo.NETWORK_TYPE_UNMETERED; break; case "not_roaming": networkTypeCode = JobInfo.NETWORK_TYPE_NOT_ROAMING; From cfdcab920ef2b82a96096d924d21dbef2bdb6ad1 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 16:48:47 +0500 Subject: [PATCH 035/142] Fixed: Exit loop after "android.nfc.tech.Ndef" found in tech list --- app/src/main/java/com/termux/api/apis/NfcAPI.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/NfcAPI.java b/app/src/main/java/com/termux/api/apis/NfcAPI.java index a9df9ee39..28ab96311 100644 --- a/app/src/main/java/com/termux/api/apis/NfcAPI.java +++ b/app/src/main/java/com/termux/api/apis/NfcAPI.java @@ -208,8 +208,10 @@ public void readNDEFTag(Intent intent, JsonWriter out) throws Exception { boolean bNdefPresent = false; String strs[] = tag.getTechList(); for (String s: strs){ - if (s.equals("android.nfc.tech.Ndef")) + if (s.equals("android.nfc.tech.Ndef")) { bNdefPresent = true; + break; + } } if (!bNdefPresent){ onUnexpectedAction(out, "Wrong Technology","termux API support only NFEF Tag"); @@ -254,8 +256,10 @@ public void readFullNDEFTag(Intent intent, JsonWriter out) throws Exception { String strs[] = tag.getTechList(); boolean bNdefPresent = false; for (String s: strs){ - if (s.equals("android.nfc.tech.Ndef")) + if (s.equals("android.nfc.tech.Ndef")) { bNdefPresent = true; + break; + } } if (!bNdefPresent){ onUnexpectedAction(out, "Wrong Technology","termux API support only NFEF Tag"); From 6ab3b48ea726d32d5491159f2efb296dd2f698e3 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 16:58:12 +0500 Subject: [PATCH 036/142] Changed: Remove unnecessary SDK_INT is always >= 24 conditions --- .../java/com/termux/api/apis/AudioAPI.java | 92 +++++++++---------- .../com/termux/api/apis/JobSchedulerAPI.java | 6 +- .../com/termux/api/apis/WallpaperAPI.java | 9 +- 3 files changed, 44 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/AudioAPI.java b/app/src/main/java/com/termux/api/apis/AudioAPI.java index 703dd1b0f..d69800b62 100644 --- a/app/src/main/java/com/termux/api/apis/AudioAPI.java +++ b/app/src/main/java/com/termux/api/apis/AudioAPI.java @@ -25,50 +25,44 @@ public static void onReceive(TermuxApiReceiver apiReceiver, final Context contex final boolean bluetootha2dp = am.isBluetoothA2dpOn(); final boolean wiredhs = am.isWiredHeadsetOn(); - final int sr, bs, sr_ll, bs_ll, sr_ps, bs_ps, nosr; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - nosr = 0; - AudioTrack at; + final int sr, bs, sr_ll, bs_ll, sr_ps, bs_ps; + AudioTrack at; + at = new AudioTrack.Builder() + .setBufferSizeInBytes(4) // one 16bit 2ch frame + .build(); + sr = at.getSampleRate(); + bs = at.getBufferSizeInFrames(); + at.release(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { at = new AudioTrack.Builder() .setBufferSizeInBytes(4) // one 16bit 2ch frame + .setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY) .build(); - sr = at.getSampleRate(); - bs = at.getBufferSizeInFrames(); - at.release(); + } else { + AudioAttributes aa = new AudioAttributes.Builder() + .setFlags(AudioAttributes.FLAG_LOW_LATENCY) + .build(); + at = new AudioTrack.Builder() + .setAudioAttributes(aa) + .setBufferSizeInBytes(4) // one 16bit 2ch frame + .build(); + } + sr_ll = at.getSampleRate(); + bs_ll = at.getBufferSizeInFrames(); + at.release(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - at = new AudioTrack.Builder() - .setBufferSizeInBytes(4) // one 16bit 2ch frame - .setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY) - .build(); - } else { - AudioAttributes aa = new AudioAttributes.Builder() - .setFlags(AudioAttributes.FLAG_LOW_LATENCY) - .build(); - at = new AudioTrack.Builder() - .setAudioAttributes(aa) - .setBufferSizeInBytes(4) // one 16bit 2ch frame - .build(); - } - sr_ll = at.getSampleRate(); - bs_ll = at.getBufferSizeInFrames(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + at = new AudioTrack.Builder() + .setBufferSizeInBytes(4) // one 16bit 2ch frame + .setPerformanceMode(AudioTrack.PERFORMANCE_MODE_POWER_SAVING) + .build(); + sr_ps = at.getSampleRate(); + bs_ps = at.getBufferSizeInFrames(); at.release(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - at = new AudioTrack.Builder() - .setBufferSizeInBytes(4) // one 16bit 2ch frame - .setPerformanceMode(AudioTrack.PERFORMANCE_MODE_POWER_SAVING) - .build(); - sr_ps = at.getSampleRate(); - bs_ps = at.getBufferSizeInFrames(); - at.release(); - } else { - sr_ps = sr; - bs_ps = bs; - } } else { - sr = bs = sr_ll = bs_ll = sr_ps = bs_ps = 0; - nosr = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); + sr_ps = sr; + bs_ps = bs; } ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() { @@ -76,19 +70,15 @@ public void writeJson(JsonWriter out) throws Exception { out.beginObject(); out.name("PROPERTY_OUTPUT_SAMPLE_RATE").value(SampleRate); out.name("PROPERTY_OUTPUT_FRAMES_PER_BUFFER").value(framesPerBuffer); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - out.name("AUDIOTRACK_SAMPLE_RATE").value(sr); - out.name("AUDIOTRACK_BUFFER_SIZE_IN_FRAMES").value(bs); - if (sr_ll != sr || bs_ll != bs) { // all or nothing - out.name("AUDIOTRACK_SAMPLE_RATE_LOW_LATENCY").value(sr_ll); - out.name("AUDIOTRACK_BUFFER_SIZE_IN_FRAMES_LOW_LATENCY").value(bs_ll); - } - if (sr_ps != sr || bs_ps != bs) { // all or nothing - out.name("AUDIOTRACK_SAMPLE_RATE_POWER_SAVING").value(sr_ps); - out.name("AUDIOTRACK_BUFFER_SIZE_IN_FRAMES_POWER_SAVING").value(bs_ps); - } - } else { - out.name("AUDIOTRACK_NATIVE_OUTPUT_SAMPLE_RATE").value(nosr); + out.name("AUDIOTRACK_SAMPLE_RATE").value(sr); + out.name("AUDIOTRACK_BUFFER_SIZE_IN_FRAMES").value(bs); + if (sr_ll != sr || bs_ll != bs) { // all or nothing + out.name("AUDIOTRACK_SAMPLE_RATE_LOW_LATENCY").value(sr_ll); + out.name("AUDIOTRACK_BUFFER_SIZE_IN_FRAMES_LOW_LATENCY").value(bs_ll); + } + if (sr_ps != sr || bs_ps != bs) { // all or nothing + out.name("AUDIOTRACK_SAMPLE_RATE_POWER_SAVING").value(sr_ps); + out.name("AUDIOTRACK_BUFFER_SIZE_IN_FRAMES_POWER_SAVING").value(bs_ps); } out.name("BLUETOOTH_A2DP_IS_ON").value(bluetootha2dp); out.name("WIREDHEADSET_IS_CONNECTED").value(wiredhs); diff --git a/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java index ea1874248..75815a3f3 100644 --- a/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java +++ b/app/src/main/java/com/termux/api/apis/JobSchedulerAPI.java @@ -117,11 +117,7 @@ public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Int jobScheduler.cancelAll(); return; } else if (cancel) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - cancelJob(apiReceiver, intent, jobScheduler, jobId); - } else { - ResultReturner.returnData(apiReceiver, intent, out -> out.println("Need at least Android N to cancel individual jobs")); - } + cancelJob(apiReceiver, intent, jobScheduler, jobId); return; } diff --git a/app/src/main/java/com/termux/api/apis/WallpaperAPI.java b/app/src/main/java/com/termux/api/apis/WallpaperAPI.java index d21fa063c..074383757 100644 --- a/app/src/main/java/com/termux/api/apis/WallpaperAPI.java +++ b/app/src/main/java/com/termux/api/apis/WallpaperAPI.java @@ -123,13 +123,8 @@ protected void onWallpaperResult(final Intent intent, WallpaperResult result) { if (result.wallpaper != null) { try { - // allow setting of lock screen wallpaper for Nougat and later - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - int flag = intent.hasExtra("lockscreen") ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM; - wallpaperManager.setBitmap(result.wallpaper, null, true, flag); - } else { - wallpaperManager.setBitmap(result.wallpaper); - } + int flag = intent.hasExtra("lockscreen") ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM; + wallpaperManager.setBitmap(result.wallpaper, null, true, flag); result.message = "Wallpaper set successfully!"; } catch (IOException e) { result.error = "Error setting wallpaper: " + e.getMessage(); From 9a1e19cf5616fef1e7e5fbf78742bf007277f14c Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 17:36:27 +0500 Subject: [PATCH 037/142] Changed: Remove unused imports --- app/src/main/java/com/termux/api/apis/DialogAPI.java | 8 -------- .../main/java/com/termux/api/apis/NotificationAPI.java | 1 - app/src/main/java/com/termux/api/apis/WallpaperAPI.java | 1 - 3 files changed, 10 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java index 9d8d2f044..5a0703c93 100644 --- a/app/src/main/java/com/termux/api/apis/DialogAPI.java +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -1,8 +1,5 @@ package com.termux.api.apis; -import static com.termux.shared.termux.TermuxConstants.TERMUX_PROPERTIES_PRIMARY_FILE_PATH; -import static com.termux.shared.termux.TermuxConstants.TERMUX_PROPERTIES_SECONDARY_FILE_PATH; - import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; @@ -53,17 +50,12 @@ import com.termux.shared.theme.NightMode; import com.termux.shared.theme.ThemeUtils; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.Objects; -import java.util.Properties; /** * API that allows receiving user input interactively in a variety of different ways diff --git a/app/src/main/java/com/termux/api/apis/NotificationAPI.java b/app/src/main/java/com/termux/api/apis/NotificationAPI.java index 979590d0b..f101d8982 100644 --- a/app/src/main/java/com/termux/api/apis/NotificationAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationAPI.java @@ -27,7 +27,6 @@ import java.io.File; import java.io.PrintWriter; import java.lang.reflect.Field; -import java.util.Arrays; import java.util.Objects; import java.util.UUID; diff --git a/app/src/main/java/com/termux/api/apis/WallpaperAPI.java b/app/src/main/java/com/termux/api/apis/WallpaperAPI.java index 074383757..3a8efe62c 100644 --- a/app/src/main/java/com/termux/api/apis/WallpaperAPI.java +++ b/app/src/main/java/com/termux/api/apis/WallpaperAPI.java @@ -6,7 +6,6 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.os.Build; import android.os.IBinder; import com.termux.api.util.ResultReturner; From 3bea194249586a7dcb143e66b46c1694cb6ca21a Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 21:56:45 +0500 Subject: [PATCH 038/142] Changed: Remove hardcoded `com.termux` values --- .../java/com/termux/api/SocketListener.java | 3 +- .../com/termux/api/TermuxAPIConstants.java | 14 ++++++++ .../TermuxApiPermissionActivity.java | 3 +- .../java/com/termux/api/apis/DialogAPI.java | 3 +- .../com/termux/api/apis/JobSchedulerAPI.java | 27 +++++++------- .../com/termux/api/apis/NotificationAPI.java | 36 +++++++++---------- .../com/termux/api/apis/StorageGetAPI.java | 3 +- .../main/java/com/termux/api/apis/UsbAPI.java | 3 +- 8 files changed, 57 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/com/termux/api/TermuxAPIConstants.java diff --git a/app/src/main/java/com/termux/api/SocketListener.java b/app/src/main/java/com/termux/api/SocketListener.java index 24980c7e8..b9b8c28ea 100644 --- a/app/src/main/java/com/termux/api/SocketListener.java +++ b/app/src/main/java/com/termux/api/SocketListener.java @@ -6,6 +6,7 @@ import android.net.LocalSocket; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants; import java.io.BufferedWriter; import java.io.DataInputStream; @@ -19,7 +20,7 @@ public class SocketListener { - public static final String LISTEN_ADDRESS = "com.termux.api://listen"; + public static final String LISTEN_ADDRESS = TermuxConstants.TERMUX_API_PACKAGE_NAME + "://listen"; private static final Pattern EXTRA_STRING = Pattern.compile("(-e|--es|--esa) +([^ ]+) +\"(.*?)(?= Build.VERSION_CODES.O) { // https://developer.android.com/about/versions/oreo/background.html - context.startForegroundService(executeIntent); + context.startForegroundService(executionIntent); } else { - context.startService(executeIntent); + context.startService(executionIntent); } Logger.logInfo(LOG_TAG, "Job started for \"" + filePath + "\""); diff --git a/app/src/main/java/com/termux/api/apis/NotificationAPI.java b/app/src/main/java/com/termux/api/apis/NotificationAPI.java index f101d8982..fedcd6f2c 100644 --- a/app/src/main/java/com/termux/api/apis/NotificationAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationAPI.java @@ -20,9 +20,13 @@ import androidx.core.util.Pair; import com.termux.api.R; +import com.termux.api.TermuxAPIConstants; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.shared.logger.Logger; +import com.termux.shared.shell.command.ExecutionCommand; +import com.termux.shared.termux.TermuxConstants; +import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE; import java.io.File; import java.io.PrintWriter; @@ -30,17 +34,11 @@ import java.util.Objects; import java.util.UUID; -import static com.termux.shared.termux.TermuxConstants.TERMUX_PREFIX_DIR_PATH; - public class NotificationAPI { private static final String LOG_TAG = "NotificationAPI"; - public static final String TERMUX_SERVICE = "com.termux.app.TermuxService"; - public static final String ACTION_EXECUTE = "com.termux.service_execute"; - public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments"; - public static final String BIN_SH = TERMUX_PREFIX_DIR_PATH+"/bin/sh"; - private static final String EXTRA_EXECUTE_IN_BACKGROUND = "com.termux.execute.background"; + public static final String BIN_SH = TermuxConstants.TERMUX_PREFIX_DIR_PATH + "/bin/sh"; private static final String CHANNEL_ID = "termux-notification"; private static final String CHANNEL_TITLE = "Termux API notification channel"; private static final String KEY_TEXT_REPLY = "TERMUX_TEXT_REPLY"; @@ -359,7 +357,7 @@ private static Intent getMessageReplyIntent(Intent oldIntent, String buttonText, String buttonAction, String notificationId) { Intent intent = oldIntent. - setClassName("com.termux.api", "com.termux.api.TermuxApiReceiver"). + setClassName(TermuxConstants.TERMUX_API_PACKAGE_NAME, TermuxAPIConstants.TERMUX_API_RECEIVER_NAME). putExtra("api_method", "NotificationReply"). putExtra("id", notificationId). putExtra("action", buttonAction). @@ -410,16 +408,18 @@ public static void onReceiveReplyToNotification(TermuxApiReceiver termuxApiRecei } static Intent createExecuteIntent(String action){ - String[] arguments = new String[]{"-c", action}; - Uri executeUri = new Uri.Builder().scheme("com.termux.file") - .path(BIN_SH) - .appendQueryParameter("arguments", Arrays.toString(arguments)) - .build(); - Intent executeIntent = new Intent(ACTION_EXECUTE, executeUri); - executeIntent.setClassName("com.termux", TERMUX_SERVICE); - executeIntent.putExtra(EXTRA_EXECUTE_IN_BACKGROUND, true); - executeIntent.putExtra(EXTRA_ARGUMENTS, arguments); - return executeIntent; + ExecutionCommand executionCommand = new ExecutionCommand(); + executionCommand.executableUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(BIN_SH).build(); + executionCommand.arguments = new String[]{"-c", action}; + executionCommand.runner = ExecutionCommand.Runner.APP_SHELL.getName(); + + // Create execution intent with the action TERMUX_SERVICE#ACTION_SERVICE_EXECUTE to be sent to the TERMUX_SERVICE + Intent executionIntent = new Intent(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE, executionCommand.executableUri); + executionIntent.setClassName(TermuxConstants.TERMUX_PACKAGE_NAME, TermuxConstants.TERMUX_APP.TERMUX_SERVICE_NAME); + executionIntent.putExtra(TERMUX_SERVICE.EXTRA_ARGUMENTS, executionCommand.arguments); + executionIntent.putExtra(TERMUX_SERVICE.EXTRA_RUNNER, executionCommand.runner); + executionIntent.putExtra(TERMUX_SERVICE.EXTRA_BACKGROUND, true); // Also pass in case user using termux-app version < 0.119.0 + return executionIntent; } static PendingIntent createAction(final Context context, String action){ diff --git a/app/src/main/java/com/termux/api/apis/StorageGetAPI.java b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java index 01ba5a3e4..f7a1d2d5c 100644 --- a/app/src/main/java/com/termux/api/apis/StorageGetAPI.java +++ b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java @@ -12,6 +12,7 @@ import com.termux.api.util.ResultReturner; import com.termux.shared.data.IntentUtils; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants; import java.io.File; import java.io.FileOutputStream; @@ -21,7 +22,7 @@ public class StorageGetAPI { - private static final String FILE_EXTRA = "com.termux.api.storage.file"; + private static final String FILE_EXTRA = TermuxConstants.TERMUX_API_PACKAGE_NAME + ".storage.file"; private static final String LOG_TAG = "StorageGetAPI"; diff --git a/app/src/main/java/com/termux/api/apis/UsbAPI.java b/app/src/main/java/com/termux/api/apis/UsbAPI.java index 74ca0fdaf..667ba1793 100644 --- a/app/src/main/java/com/termux/api/apis/UsbAPI.java +++ b/app/src/main/java/com/termux/api/apis/UsbAPI.java @@ -15,6 +15,7 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants; import java.io.IOException; import java.io.PrintWriter; @@ -112,7 +113,7 @@ private static boolean requestPermission(final @NonNull UsbDevice device, final Looper looper = Looper.myLooper(); final boolean[] result = new boolean[1]; - final String ACTION_USB_PERMISSION = "com.termux.api.USB_PERMISSION"; + final String ACTION_USB_PERMISSION = TermuxConstants.TERMUX_API_PACKAGE_NAME + ".USB_PERMISSION"; final BroadcastReceiver usbReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context usbContext, final Intent usbIntent) { From 9a9e3eaee6748e19616d51615b11920c0a6a4b2c Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 22:03:02 +0500 Subject: [PATCH 039/142] Changed|Fixed: Use TERMUX_API_FILE_SHARE_URI_AUTHORITY for content provided uri and fix sharing paths with "#" --- app/src/main/java/com/termux/api/TermuxAPIConstants.java | 3 +++ app/src/main/java/com/termux/api/apis/ShareAPI.java | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/termux/api/TermuxAPIConstants.java b/app/src/main/java/com/termux/api/TermuxAPIConstants.java index ca25ca8ce..e27e5b987 100644 --- a/app/src/main/java/com/termux/api/TermuxAPIConstants.java +++ b/app/src/main/java/com/termux/api/TermuxAPIConstants.java @@ -11,4 +11,7 @@ public class TermuxAPIConstants { */ public static final String TERMUX_API_RECEIVER_NAME = TERMUX_API_PACKAGE_NAME + ".TermuxApiReceiver"; // Default to "com.termux.api.TermuxApiReceiver" + /** The Uri authority for Termux:API app file shares */ + public static final String TERMUX_API_FILE_SHARE_URI_AUTHORITY = TERMUX_PACKAGE_NAME + ".sharedfiles"; // Default: "com.termux.sharedfiles" + } diff --git a/app/src/main/java/com/termux/api/apis/ShareAPI.java b/app/src/main/java/com/termux/api/apis/ShareAPI.java index db405481a..a8eeb7655 100644 --- a/app/src/main/java/com/termux/api/apis/ShareAPI.java +++ b/app/src/main/java/com/termux/api/apis/ShareAPI.java @@ -12,9 +12,11 @@ import android.webkit.MimeTypeMap; import com.termux.api.R; +import com.termux.api.TermuxAPIConstants; import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.shared.logger.Logger; +import com.termux.shared.net.uri.UriUtils; import java.io.File; import java.io.FileNotFoundException; @@ -88,7 +90,9 @@ public void writeResult(PrintWriter out) { Intent sendIntent = new Intent(); sendIntent.setAction(finalIntentAction); - Uri uriToShare = Uri.parse("content://com.termux.sharedfiles" + fileToShare.getAbsolutePath()); + + // Do not create Uri with Uri.parse() and use Uri.Builder().path(), check UriUtils.getUriFilePath(). + Uri uriToShare = UriUtils.getContentUri(TermuxAPIConstants.TERMUX_API_FILE_SHARE_URI_AUTHORITY, fileToShare.getAbsolutePath()); sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); String contentTypeToUse; From 94d478d233b589a738c219d0af3b96cb605e93de Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 22:10:27 +0500 Subject: [PATCH 040/142] Fixed: Fix potential NullPointerException in NotificationAPI $REPLY parsing --- app/src/main/java/com/termux/api/apis/NotificationAPI.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/NotificationAPI.java b/app/src/main/java/com/termux/api/apis/NotificationAPI.java index fedcd6f2c..7be15581d 100644 --- a/app/src/main/java/com/termux/api/apis/NotificationAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationAPI.java @@ -385,8 +385,11 @@ public static void onReceiveReplyToNotification(TermuxApiReceiver termuxApiRecei String replyKey = intent.getStringExtra("replyKey"); CharSequence reply = getMessageText(intent); - String action = intent.getStringExtra("action") - .replace("$REPLY", shellEscape(reply)); + String action = intent.getStringExtra("action"); + + if (action != null && reply != null) + action = action.replace("$REPLY", shellEscape(reply)); + try { createAction(context, action).send(); } catch (PendingIntent.CanceledException e) { From 237d36a527b60cd0d171bb4b1c5eb5d2db728f0b Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 22:13:11 +0500 Subject: [PATCH 041/142] Fixed: Fix raw use of parameterized class 'Pair' for NotificationAPI --- app/src/main/java/com/termux/api/apis/NotificationAPI.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/termux/api/apis/NotificationAPI.java b/app/src/main/java/com/termux/api/apis/NotificationAPI.java index 7be15581d..33fd72935 100644 --- a/app/src/main/java/com/termux/api/apis/NotificationAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationAPI.java @@ -306,7 +306,8 @@ static Pair buildNotification(final Context PendingIntent pi = createAction(context, onDeleteActionExtra); notification.setDeleteIntent(pi); } - return new Pair(notification, notificationId); + + return new Pair<>(notification, notificationId); } private static String getNotificationId(Intent intent) { From cc186d0383e25640486d90c108c4ab8f904962bf Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 22:45:40 +0500 Subject: [PATCH 042/142] Changed|Fixed: Fix potential NullPointerException in PhotoAPI and use FileUtils for photo directory creation --- .../java/com/termux/api/apis/PhotoAPI.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/PhotoAPI.java b/app/src/main/java/com/termux/api/apis/PhotoAPI.java index 8b04c5796..0235538a2 100644 --- a/app/src/main/java/com/termux/api/apis/PhotoAPI.java +++ b/app/src/main/java/com/termux/api/apis/PhotoAPI.java @@ -22,7 +22,10 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; +import com.termux.shared.errors.Error; +import com.termux.shared.file.FileUtils; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.file.TermuxFileUtils; import java.io.File; import java.io.FileOutputStream; @@ -43,16 +46,32 @@ public static void onReceive(TermuxApiReceiver apiReceiver, final Context contex Logger.logDebug(LOG_TAG, "onReceive"); final String filePath = intent.getStringExtra("file"); - final File outputFile = new File(filePath); - final File outputDir = outputFile.getParentFile(); final String cameraId = Objects.toString(intent.getStringExtra("camera"), "0"); ResultReturner.returnData(apiReceiver, intent, stdout -> { - if (!(outputDir.isDirectory() || outputDir.mkdirs())) { - stdout.println("Not a folder (and unable to create it): " + outputDir.getAbsolutePath()); - } else { - takePicture(stdout, context, outputFile, cameraId); + if (filePath == null || filePath.isEmpty()) { + stdout.println("ERROR: " + "File path not passed"); + return; } + + // Get canonical path of filePath + String photoFilePath = TermuxFileUtils.getCanonicalPath(filePath, null, true); + String photoDirPath = FileUtils.getFileBasename(photoFilePath); + + // If workingDirectory is not a directory, or is not readable or writable, then just return + // Creation of missing directory and setting of read, write and execute permissions are only done if workingDirectory is + // under allowed termux working directory paths. + // We try to set execute permissions, but ignore if they are missing, since only read and write permissions are required + // for working directories. + Error error = TermuxFileUtils.validateDirectoryFileExistenceAndPermissions("photo directory", photoDirPath, + true, true, true, + false, true); + if (error != null) { + stdout.println("ERROR: " + error.getErrorLogString()); + return; + } + + takePicture(stdout, context, new File(photoFilePath), cameraId); }); } From 950f9be3c9027bf7c6797a0ae45a8e495eae723e Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 22:55:43 +0500 Subject: [PATCH 043/142] Changed|Fixed: Fix potential NullPointerException in StorageGetAPI and use FileUtils for missing permissions check --- .../com/termux/api/apis/StorageGetAPI.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/StorageGetAPI.java b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java index f7a1d2d5c..abfd2c697 100644 --- a/app/src/main/java/com/termux/api/apis/StorageGetAPI.java +++ b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java @@ -11,10 +11,12 @@ import com.termux.api.TermuxApiReceiver; import com.termux.api.util.ResultReturner; import com.termux.shared.data.IntentUtils; +import com.termux.shared.errors.Error; +import com.termux.shared.file.FileUtils; import com.termux.shared.logger.Logger; import com.termux.shared.termux.TermuxConstants; +import com.termux.shared.termux.file.TermuxFileUtils; -import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -31,13 +33,24 @@ public static void onReceive(TermuxApiReceiver apiReceiver, final Context contex ResultReturner.returnData(apiReceiver, intent, out -> { final String fileExtra = intent.getStringExtra("file"); - if (fileExtra == null || !new File(fileExtra).getParentFile().canWrite()) { - out.println("ERROR: Not a writable folder: " + fileExtra); + if (fileExtra == null || fileExtra.isEmpty()) { + out.println("ERROR: " + "File path not passed"); + + return; + } + + // Get canonical path of fileExtra + String filePath = TermuxFileUtils.getCanonicalPath(fileExtra, null, true); + String fileParentDirPath = FileUtils.getFileBasename(filePath); + + Error error = FileUtils.checkMissingFilePermissions("file parent directory", fileParentDirPath, "rw-", true); + if (error != null) { + out.println("ERROR: " + error.getErrorLogString()); return; } Intent intent1 = new Intent(context, StorageActivity.class); - intent1.putExtra(FILE_EXTRA, fileExtra); + intent1.putExtra(FILE_EXTRA, filePath); intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent1); }); From e7a684a9942e3d720975eb90e14e5f71bd6cb9b0 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 22:58:06 +0500 Subject: [PATCH 044/142] Changed: Remove redundant return variables --- app/src/main/java/com/termux/api/apis/DialogAPI.java | 3 +-- .../java/com/termux/api/apis/NotificationAPI.java | 11 +++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java index 618183d9b..84092cddb 100644 --- a/app/src/main/java/com/termux/api/apis/DialogAPI.java +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -544,8 +544,7 @@ static class TimeInputMethod extends InputDialog { @Override String getResult() { - String result = String.format(Locale.getDefault(), "%02d:%02d", widgetView.getHour(), widgetView.getMinute()); - return result; + return String.format(Locale.getDefault(), "%02d:%02d", widgetView.getHour(), widgetView.getMinute()); } @Override diff --git a/app/src/main/java/com/termux/api/apis/NotificationAPI.java b/app/src/main/java/com/termux/api/apis/NotificationAPI.java index 33fd72935..c427fff2f 100644 --- a/app/src/main/java/com/termux/api/apis/NotificationAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationAPI.java @@ -331,9 +331,8 @@ static NotificationCompat.Action createReplyAction(final Context context, Intent int buttonNum, String buttonText, String buttonAction, String notificationId) { - String replyLabel = buttonText; RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY) - .setLabel(replyLabel) + .setLabel(buttonText) .build(); // Build a PendingIntent for the reply action to trigger. @@ -344,26 +343,22 @@ static NotificationCompat.Action createReplyAction(final Context context, Intent PendingIntent.FLAG_UPDATE_CURRENT); // Create the reply action and add the remote input. - NotificationCompat.Action action = - new NotificationCompat.Action.Builder(R.drawable.ic_event_note_black_24dp, + return new NotificationCompat.Action.Builder(R.drawable.ic_event_note_black_24dp, buttonText, replyPendingIntent) .addRemoteInput(remoteInput) .build(); - - return action; } private static Intent getMessageReplyIntent(Intent oldIntent, String buttonText, String buttonAction, String notificationId) { - Intent intent = oldIntent. + return oldIntent. setClassName(TermuxConstants.TERMUX_API_PACKAGE_NAME, TermuxAPIConstants.TERMUX_API_RECEIVER_NAME). putExtra("api_method", "NotificationReply"). putExtra("id", notificationId). putExtra("action", buttonAction). putExtra("replyKey", buttonText); - return intent; } From 916f321c076066f15db92cf764bdd2916e3898b6 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 23:00:18 +0500 Subject: [PATCH 045/142] Changed: Replace c-style array declarations with java style --- app/src/main/java/com/termux/api/apis/NfcAPI.java | 12 ++++++------ .../main/java/com/termux/api/apis/TelephonyAPI.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/NfcAPI.java b/app/src/main/java/com/termux/api/apis/NfcAPI.java index 28ab96311..a402c1384 100644 --- a/app/src/main/java/com/termux/api/apis/NfcAPI.java +++ b/app/src/main/java/com/termux/api/apis/NfcAPI.java @@ -206,7 +206,7 @@ public void readNDEFTag(Intent intent, JsonWriter out) throws Exception { Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); Ndef ndefTag = Ndef.get(tag); boolean bNdefPresent = false; - String strs[] = tag.getTechList(); + String[] strs = tag.getTechList(); for (String s: strs){ if (s.equals("android.nfc.tech.Ndef")) { bNdefPresent = true; @@ -220,7 +220,7 @@ public void readNDEFTag(Intent intent, JsonWriter out) throws Exception { NdefMessage[] nmsgs = new NdefMessage[msgs.length]; if (msgs.length == 1) { nmsgs[0] = (NdefMessage) msgs[0]; - NdefRecord records[] = nmsgs[0].getRecords(); + NdefRecord[] records = nmsgs[0].getRecords(); out.beginObject(); if (records.length >0 ) { { @@ -232,7 +232,7 @@ public void readNDEFTag(Intent intent, JsonWriter out) throws Exception { int pos = 1 + record.getPayload()[0]; pos = (NdefRecord.TNF_WELL_KNOWN==record.getTnf())?(int)record.getPayload()[0]+1:0; int len = record.getPayload().length - pos; - byte msg[] = new byte[len]; + byte[] msg = new byte[len]; System.arraycopy(record.getPayload(), pos, msg, 0, len); out.name("Payload").value(new String(msg)); out.endObject(); @@ -253,7 +253,7 @@ public void readFullNDEFTag(Intent intent, JsonWriter out) throws Exception { Ndef ndefTag = Ndef.get(tag); Parcelable[] msgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); - String strs[] = tag.getTechList(); + String[] strs = tag.getTechList(); boolean bNdefPresent = false; for (String s: strs){ if (s.equals("android.nfc.tech.Ndef")) { @@ -286,7 +286,7 @@ public void readFullNDEFTag(Intent intent, JsonWriter out) throws Exception { if (msgs.length == 1) { Logger.logInfo(LOG_TAG, "-->> readFullNDEFTag - 06"); nmsgs[0] = (NdefMessage) msgs[0]; - NdefRecord records[] = nmsgs[0].getRecords(); + NdefRecord[] records = nmsgs[0].getRecords(); { out.name("record"); if (records.length > 1) @@ -300,7 +300,7 @@ public void readFullNDEFTag(Intent intent, JsonWriter out) throws Exception { int pos = 1 + record.getPayload()[0]; pos = (NdefRecord.TNF_WELL_KNOWN==record.getTnf())?(int)record.getPayload()[0]+1:0; int len = record.getPayload().length - pos; - byte msg[] = new byte[len]; + byte[] msg = new byte[len]; System.arraycopy(record.getPayload(), pos, msg, 0, len); out.name("payload").value(new String(msg)); out.endObject(); diff --git a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java index 8a0e7bb27..6a751e6c8 100644 --- a/app/src/main/java/com/termux/api/apis/TelephonyAPI.java +++ b/app/src/main/java/com/termux/api/apis/TelephonyAPI.java @@ -41,7 +41,7 @@ private static void writeIfKnown(JsonWriter out, String name, int value) throws private static void writeIfKnown(JsonWriter out, String name, long value) throws IOException { if (value != Long.MAX_VALUE) out.name(name).value(value); } - private static void writeIfKnown(JsonWriter out, String name, int value[]) throws IOException { + private static void writeIfKnown(JsonWriter out, String name, int[] value) throws IOException { if (value != null) { out.name(name); out.beginArray(); From 99080bab38db0d666cc9704de3ee00941b6b0733 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 23:14:02 +0500 Subject: [PATCH 046/142] Fixed: Fix NullPointerException when running bell/vibrate on Samsung devices on android 8 and handled deprecated code Apparently occurs on only Samsung android 8 devices and there is no fix for vibrator except catching the exception so that app doesn't crash. https://gitlab.com/juanitobananas/wave-up/-/issues/131 https://github.com/overbound/SonicTimeTwisted/issues/131 https://web.archive.org/web/20201114040257/https://www.badlogicgames.com/forum/viewtopic.php?t=28507 ``` java.lang.NullPointerException: Attempt to read from field 'android.os.VibrationEffect com.android.server.VibratorService$Vibration.mEffect' on a null object reference at android.os.Parcel.readException(Parcel.java:2035) at android.os.Parcel.readException(Parcel.java:1975) at android.os.IVibratorService$Stub$Proxy.vibrate(IVibratorService.java:292) at android.os.SystemVibrator.vibrate(SystemVibrator.java:81) at android.os.Vibrator.vibrate(Vibrator.java:191) at android.os.Vibrator.vibrate(Vibrator.java:110) at android.os.Vibrator.vibrate(Vibrator.java:89) at com.termux.app.terminal.io.BellHandler$1.run(BellHandler.java:37) at com.termux.app.terminal.io.BellHandler.doBell(BellHandler.java:55) at com.termux.app.terminal.TermuxTerminalSessionClient.onBell(TermuxTerminalSessionClient.java:178) at com.termux.terminal.TerminalSession.onBell(TerminalSession.java:278) ``` --- .../java/com/termux/api/apis/VibrateAPI.java | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/VibrateAPI.java b/app/src/main/java/com/termux/api/apis/VibrateAPI.java index 4892b0e19..986764cb9 100644 --- a/app/src/main/java/com/termux/api/apis/VibrateAPI.java +++ b/app/src/main/java/com/termux/api/apis/VibrateAPI.java @@ -3,6 +3,8 @@ import android.content.Context; import android.content.Intent; import android.media.AudioManager; +import android.os.Build; +import android.os.VibrationEffect; import android.os.Vibrator; import com.termux.api.TermuxApiReceiver; @@ -16,16 +18,34 @@ public class VibrateAPI { public static void onReceive(TermuxApiReceiver apiReceiver, Context context, Intent intent) { Logger.logDebug(LOG_TAG, "onReceive"); - Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - int milliseconds = intent.getIntExtra("duration_ms", 1000); - boolean force = intent.getBooleanExtra("force", false); - - AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - if (am.getRingerMode() == AudioManager.RINGER_MODE_SILENT && !force) { - // Not vibrating since in silent mode and -f/--force option not used. - } else { - vibrator.vibrate(milliseconds); - } + new Thread() { + @Override + public void run() { + Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + int milliseconds = intent.getIntExtra("duration_ms", 1000); + boolean force = intent.getBooleanExtra("force", false); + + AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + if (am == null) { + Logger.logError(LOG_TAG, "Audio service null"); + return; + } + // Do not vibrate if in silent mode and -f/--force option is not used. + if (am.getRingerMode() != AudioManager.RINGER_MODE_SILENT || force) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE)); + } else { + vibrator.vibrate(milliseconds); + } + } catch (Exception e) { + // Issue on samsung devices on android 8 + // java.lang.NullPointerException: Attempt to read from field 'android.os.VibrationEffect com.android.server.VibratorService$Vibration.mEffect' on a null object reference + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to run vibrator", e); + } + } + } + }.start(); ResultReturner.noteDone(apiReceiver, intent); } From 4c5c27bbad3e2e5f6209bf6d5601d706e7454faa Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 14 Mar 2022 23:25:53 +0500 Subject: [PATCH 047/142] Fixed: Catch IllegalStateException "Broadcast already finished" and any other exceptions thrown while finishing ResultSender --- .../java/com/termux/api/util/ResultReturner.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java index 5766717d9..5eb799b6f 100644 --- a/app/src/main/java/com/termux/api/util/ResultReturner.java +++ b/app/src/main/java/com/termux/api/util/ResultReturner.java @@ -190,10 +190,14 @@ public static void returnData(Object context, final Intent intent, final ResultW activity.setResult(1); } } finally { - if (asyncResult != null) { - asyncResult.finish(); - } else if (activity != null) { - activity.finish(); + try { + if (asyncResult != null) { + asyncResult.finish(); + } else if (activity != null) { + activity.finish(); + } + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to cleanup", e); } } }; From 166595c8b3efd07b178ad81d6b00e560e51039ab Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 17 Mar 2022 01:17:18 +0500 Subject: [PATCH 048/142] Fixed: Prevent multiple calls to `ResultReturner.returnData()` in `DialogAPI` button press which throws `IOException` due to connecting to output socket again ``` E/TermuxAPI.ResultReturner: Error in ResultReturner: java.io.IOException: Connection refused at android.net.LocalSocketImpl.connectLocal(Native Method) at android.net.LocalSocketImpl.connect(LocalSocketImpl.java:262) at android.net.LocalSocket.connect(LocalSocket.java:148) at com.termux.api.util.ResultReturner.lambda$returnData$0(ResultReturner.java:158) ``` --- .../main/java/com/termux/api/apis/DialogAPI.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java index 84092cddb..5d28e355e 100644 --- a/app/src/main/java/com/termux/api/apis/DialogAPI.java +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -77,7 +77,7 @@ public static class DialogActivity extends AppCompatActivity { private static final String LOG_TAG = "DialogActivity"; - private boolean resultReturned = false; + private volatile boolean resultReturned = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -117,9 +117,7 @@ protected void onDestroy() { super.onDestroy(); - if (!resultReturned) { - postResult(this, null); - } + postResult(this, null); } /** @@ -144,7 +142,14 @@ static String[] getInputValues(Intent intent) { /** * Writes the InputResult to the console */ - protected void postResult(final Context context, final InputResult result) { + protected synchronized void postResult(final Context context, final InputResult resultParam) { + if (resultReturned) { + Logger.logDebug(LOG_TAG, "Ignoring call to postResult"); + return; + } else { + Logger.logDebug(LOG_TAG, "postResult"); + } + ResultReturner.returnData(context, getIntent(), new ResultReturner.ResultJsonWriter() { @Override From 364fe3395b2a0e65c8c0ea1b46dacb00aed9e3fa Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 17 Mar 2022 01:19:56 +0500 Subject: [PATCH 049/142] Fixed: Fix `NullPointerException` if activity is dismissed while dialog is open This is due to `DialogActivity.onDestroy()` calling `postResult()` will `null` `result`. --- app/src/main/java/com/termux/api/apis/DialogAPI.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java index 5d28e355e..78bd08503 100644 --- a/app/src/main/java/com/termux/api/apis/DialogAPI.java +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -156,6 +156,12 @@ protected synchronized void postResult(final Context context, final InputResult public void writeJson(JsonWriter out) throws Exception { out.beginObject(); + InputResult result = resultParam; + if (result == null) { + result = new InputResult(); + result.code = Dialog.BUTTON_NEGATIVE; + } + out.name("code").value(result.code); out.name("text").value(result.text); if(result.index > -1) { From ea5c14b40afbc029c5b223b44ba5e36cab9700eb Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 17 Mar 2022 01:49:03 +0500 Subject: [PATCH 050/142] Fixed: Dismiss dialog before `DialogActivity.onDestroy()` to prevent `WindowLeaked` exception ``` E/WindowManager: android.view.WindowLeaked: Activity com.termux.api.apis.DialogAPI$DialogActivity has leaked window DecorView@6f98319[DialogAPI$DialogActivity] that was originally added here at android.view.ViewRootImpl.(ViewRootImpl.java:736) at android.view.ViewRootImpl.(ViewRootImpl.java:720) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:399) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:109) at android.app.Dialog.show(Dialog.java:340) at com.termux.api.apis.DialogAPI$DialogActivity$InputDialog.create(DialogAPI.java:967) at com.termux.api.apis.DialogAPI$DialogActivity.onCreate(DialogAPI.java:102) ``` --- .../java/com/termux/api/apis/DialogAPI.java | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java index 78bd08503..9c365ae0f 100644 --- a/app/src/main/java/com/termux/api/apis/DialogAPI.java +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -78,6 +78,7 @@ public static class DialogActivity extends AppCompatActivity { private static final String LOG_TAG = "DialogActivity"; private volatile boolean resultReturned = false; + private InputMethod mInputMethod; @Override protected void onCreate(Bundle savedInstanceState) { @@ -96,11 +97,17 @@ protected void onCreate(Bundle savedInstanceState) { if (shouldEnableDarkTheme) this.setTheme(R.style.DialogTheme_Dark); - InputMethod method = InputMethodFactory.get(methodType, this); - method.create(this, result -> { + mInputMethod = InputMethodFactory.get(methodType, this); + if (mInputMethod != null) { + mInputMethod.create(this, result -> { + postResult(context, result); + finish(); + }); + } else { + InputResult result = new InputResult(); + result.error = "Unknown Input Method: " + methodType; postResult(context, result); - finish(); - }); + } } @Override @@ -118,6 +125,20 @@ protected void onDestroy() { super.onDestroy(); postResult(this, null); + + if (mInputMethod != null) { + Dialog dialog = mInputMethod.getDialog(); + dismissDialog(dialog); + } + } + + private static void dismissDialog(Dialog dialog) { + try { + if (dialog != null) + dialog.dismiss(); + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed tp dismiss dialog", e); + } } /** @@ -219,11 +240,7 @@ public static InputMethod get(final String type, final AppCompatActivity activit case "time": return new TimeInputMethod(activity); default: - return (activity1, resultListener) -> { - InputResult result = new InputResult(); - result.error = "Unknown Input Method: " + type; - resultListener.onResult(result); - }; + return null; } } } @@ -233,6 +250,8 @@ public static InputMethod get(final String type, final AppCompatActivity activit * Interface for creating an input method type */ interface InputMethod { + Dialog getDialog(); + void create(AppCompatActivity activity, InputResultListener resultListener); } @@ -410,7 +429,7 @@ void updateCounterRange() { if (values.length != RANGE_LENGTH) { inputResult.error = "Invalid range! Must be 3 int values!"; postCanceledResult(); - dialog.dismiss(); + dismissDialog(dialog); } else { min = Math.min(values[0], values[1]); max = Math.max(values[0], values[1]); @@ -674,7 +693,7 @@ public void setupDialog(final Dialog dialog, int style) { InputResult result = new InputResult(); result.text = values[j]; result.index = j; - dialog.dismiss(); + dismissDialog(dialog); resultListener.onResult(result); }); @@ -940,6 +959,10 @@ String getResult() { initActivityDisplay(activity); } + @Override + public Dialog getDialog() { + return dialog; + } @Override public void create(AppCompatActivity activity, final InputResultListener resultListener) { From 30a58d7ec96892fc79380d2ac49fbcbb31383a69 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 17 Mar 2022 02:07:45 +0500 Subject: [PATCH 051/142] Fixed: Fix `NullPointerException` triggered when trying to show keyboard without dialog view in focus by pressing outside of bottom sheets dialog `termux-dialog sheet -v 'Item One','Item Two'` ``` java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.IBinder android.view.View.getWindowToken()' on a null object reference at android.view.inputmethod.InputMethodManager.showSoftInput(InputMethodManager.java:1668) at android.view.inputmethod.InputMethodManager.showSoftInput(InputMethodManager.java:1592) at com.termux.api.apis.DialogAPI$DialogActivity$BottomSheetInputMethod.showKeyboard(DialogAPI.java:719) at com.termux.api.apis.DialogAPI$DialogActivity$BottomSheetInputMethod$1.cancel(DialogAPI.java:666) at com.google.android.material.bottomsheet.BottomSheetDialog$2.onClick(BottomSheetDialog.java:327) ``` Closes #486 --- app/src/main/java/com/termux/api/apis/DialogAPI.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/DialogAPI.java b/app/src/main/java/com/termux/api/apis/DialogAPI.java index 9c365ae0f..c3c9d2c79 100644 --- a/app/src/main/java/com/termux/api/apis/DialogAPI.java +++ b/app/src/main/java/com/termux/api/apis/DialogAPI.java @@ -50,6 +50,7 @@ import com.termux.shared.termux.theme.TermuxThemeUtils; import com.termux.shared.theme.NightMode; import com.termux.shared.theme.ThemeUtils; +import com.termux.shared.view.KeyboardUtils; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -712,15 +713,11 @@ public void setupDialog(final Dialog dialog, int style) { */ protected void hideKeyboard() { - Objects.requireNonNull(getDialog()).getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + KeyboardUtils.setSoftKeyboardAlwaysHiddenFlags(getActivity()); } protected void showKeyboard() { - getInputMethodManager().showSoftInput(getView(), InputMethodManager.SHOW_FORCED); - } - - protected InputMethodManager getInputMethodManager() { - return (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE); + KeyboardUtils.showSoftKeyboard(getActivity(), getView()); } /** From ec390259d9e709ba5bc99ecd79e0fbea503e9b9d Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 17 Mar 2022 02:15:18 +0500 Subject: [PATCH 052/142] Added: Add storage permissions since `PermissionUtils.checkPermissions()` will check if app has requested them before checking if permission is granted --- app/src/main/AndroidManifest.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aaf3d4aaf..0d36a6137 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,12 +15,14 @@ + + @@ -31,6 +33,7 @@ + From cfbcde8fbec85edcd4f276f36c19ce587aa07edb Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 18 Mar 2022 06:51:03 +0500 Subject: [PATCH 053/142] Changed: Bump termux-shared to caa13b7047 Required commits termux/termux-app@c3ac30e2 termux/termux-app@792c33c9 termux/termux-app@021cb60e termux/termux-app@621545dd termux/termux-app@4b07e4f4 termux/termux-app@5f005313 termux/termux-app@477b36ac `UncaughtExceptions` on main thread will now send `ACTION_NOTIFY_APP_CRASH` to `termux-app` to notify user of crash --- app/build.gradle | 2 +- build.gradle | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index deb98cb5d..df0efafd8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,7 +67,7 @@ dependencies { implementation 'androidx.biometric:biometric:1.2.0-alpha03' implementation 'androidx.media:media:1.4.3' - implementation 'com.termux.termux-app:termux-shared:760ae78aff' + implementation 'com.termux.termux-app:termux-shared:caa13b7047' // Use if below libraries are published locally by termux-app with `./gradlew publishReleasePublicationToMavenLocal` and used with `mavenLocal()`. // If updates are done, republish there and sync project with gradle files here // https://github.com/termux/termux-app/wiki/Termux-Libraries diff --git a/build.gradle b/build.gradle index 5675f8e61..bde09549e 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,7 @@ allprojects { google() mavenCentral() maven { url "https://jitpack.io" } + //mavenLocal() } } From c3befab6b58777b779fc171330c48556d2730cfc Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 18 Mar 2022 06:51:29 +0500 Subject: [PATCH 054/142] Changed: Use `PermissionUtils` for permission checking --- .../termux/api/activities/TermuxApiPermissionActivity.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java b/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java index 80922b9aa..a7a2cb634 100644 --- a/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java +++ b/app/src/main/java/com/termux/api/activities/TermuxApiPermissionActivity.java @@ -3,11 +3,11 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.text.TextUtils; import android.util.JsonWriter; import com.termux.api.util.ResultReturner; +import com.termux.shared.android.PermissionUtils; import com.termux.shared.logger.Logger; import com.termux.shared.termux.TermuxConstants; @@ -30,7 +30,7 @@ public class TermuxApiPermissionActivity extends Activity { public static boolean checkAndRequestPermissions(Context context, Intent intent, String... permissions) { final ArrayList permissionsToRequest = new ArrayList<>(); for (String permission : permissions) { - if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_DENIED) { + if (!PermissionUtils.checkPermission(context, permission)) { permissionsToRequest.add(permission); } } @@ -72,7 +72,7 @@ protected void onResume() { super.onResume(); ArrayList permissionValues = getIntent().getStringArrayListExtra(PERMISSIONS_EXTRA); - requestPermissions(permissionValues.toArray(new String[0]), 123); + PermissionUtils.requestPermissions(this, permissionValues.toArray(new String[0]), 0); finish(); } From f2c1342f6f89aaf527df16c923dda0d4cae73faa Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 18 Mar 2022 06:59:53 +0500 Subject: [PATCH 055/142] Added: Add launcher icon/activity This must not be allowed to be disabled since there is no other way to start Termux:API app again if it crashes multiple times and gets marked as bad process by android. This is also required to bypass OEM battery restrictions like DuraSpeed. This also easily allows users to know if the app is installed or not. Related issue termux/termux-widget#56 Related issue #447 --- app/src/main/AndroidManifest.xml | 24 +++++ .../api/activities/TermuxAPIActivity.java | 99 +++++++++++++++++++ .../main/res/layout/activity_termux_api.xml | 40 ++++++++ app/src/main/res/menu/activity_termux_api.xml | 20 ++++ app/src/main/res/values/strings.xml | 19 ++++ 5 files changed, 202 insertions(+) create mode 100644 app/src/main/java/com/termux/api/activities/TermuxAPIActivity.java create mode 100644 app/src/main/res/layout/activity_termux_api.xml create mode 100644 app/src/main/res/menu/activity_termux_api.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0d36a6137..f764d50f8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,6 +65,16 @@ android:theme="@android:style/Theme.Material.Light" tools:ignore="GoogleAppIndexingWarning"> + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/activity_termux_api.xml b/app/src/main/res/menu/activity_termux_api.xml new file mode 100644 index 000000000..bb7a36fef --- /dev/null +++ b/app/src/main/res/menu/activity_termux_api.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 682868c7e..3abdde809 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,4 +13,23 @@ Grant permission This service keeps Termux:API running in the background for faster startup of termux-* commands. This app needs the following permission(s):\n + + &TERMUX_API_APP_NAME; is a plugin app for the &TERMUX_APP_NAME; app + that executes termux-api package commands. + Check &TERMUX_APP_NAME; app github %1$s, &TERMUX_API_APP_NAME; app github %2$s and + %3$s package github %4$s for more info. + + \n\nThe &TERMUX_API_APP_NAME; app requires `%3$s` apt package to function. + Run `pkg install %3$s` to install it. + + \n\nNote that if &TERMUX_API_APP_NAME; app crashes too many times, then android will mark the + app as a bad process and you will need to manually start this activity again once for the + api commands to start working again, otherwise the commands will hang. + + \n\nReports for some crashes may be shown when you restart &TERMUX_APP_NAME; app. + + + Info + Settings + From 6112bf67ba07a7005197cedbb337437b04e51a0a Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 18 Mar 2022 07:08:17 +0500 Subject: [PATCH 056/142] Added: Add support to send crash notifications if exceptions are caught in `TermuxApiReceiver` and `ResultReturner` This will allow users to know if an exception caused an API command to fail instead of having to check `logcat`. Currently, `Termux:API` app is broken a lot and crash reports from users for edge case exceptions or device specific exceptions will help solve such issues. In future, likely `stderr` will be used instead, once support is added. --- .../com/termux/api/TermuxAPIApplication.java | 2 ++ .../java/com/termux/api/TermuxApiReceiver.java | 8 +++++++- .../com/termux/api/util/ResultReturner.java | 18 +++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/termux/api/TermuxAPIApplication.java b/app/src/main/java/com/termux/api/TermuxAPIApplication.java index 0a8f74836..0f2b7d695 100644 --- a/app/src/main/java/com/termux/api/TermuxAPIApplication.java +++ b/app/src/main/java/com/termux/api/TermuxAPIApplication.java @@ -3,6 +3,7 @@ import android.app.Application; import android.content.Context; +import com.termux.api.util.ResultReturner; import com.termux.shared.logger.Logger; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.crash.TermuxCrashUtils; @@ -16,6 +17,7 @@ public void onCreate() { // Set crash handler for the app TermuxCrashUtils.setCrashHandler(this); + ResultReturner.setContext(this); // Set log config for the app setLogConfig(getApplicationContext(), true); diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index 8c5392d38..2d7fe5123 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -48,6 +48,8 @@ import com.termux.api.activities.TermuxApiPermissionActivity; import com.termux.shared.data.IntentUtils; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants; +import com.termux.shared.termux.crash.TermuxCrashUtils; public class TermuxApiReceiver extends BroadcastReceiver { @@ -61,9 +63,13 @@ public void onReceive(Context context, Intent intent) { try { doWork(context, intent); } catch (Exception e) { + String message = "Error in " + LOG_TAG; // Make sure never to throw exception from BroadCastReceiver to avoid "process is bad" // behaviour from the Android system. - Logger.logStackTraceWithMessage(LOG_TAG, "Error in TermuxApiReceiver", e); + Logger.logStackTraceWithMessage(LOG_TAG, message, e); + + TermuxCrashUtils.sendPluginCrashReportNotification(context, LOG_TAG, + TermuxConstants.TERMUX_API_APP_NAME + " Error", message, e); } } diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java index 5eb799b6f..60c9a762a 100644 --- a/app/src/main/java/com/termux/api/util/ResultReturner.java +++ b/app/src/main/java/com/termux/api/util/ResultReturner.java @@ -1,9 +1,11 @@ package com.termux.api.util; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.IntentService; import android.content.BroadcastReceiver; import android.content.BroadcastReceiver.PendingResult; +import android.content.Context; import android.content.Intent; import android.net.LocalSocket; import android.net.LocalSocketAddress; @@ -11,6 +13,8 @@ import android.util.JsonWriter; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants; +import com.termux.shared.termux.crash.TermuxCrashUtils; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; @@ -21,6 +25,9 @@ public abstract class ResultReturner { + @SuppressLint("StaticFieldLeak") + private static Context context; + private static final String LOG_TAG = "ResultReturner"; /** @@ -183,7 +190,12 @@ public static void returnData(Object context, final Intent intent, final ResultW activity.setResult(0); } } catch (Exception e) { - Logger.logStackTraceWithMessage(LOG_TAG, "Error in ResultReturner", e); + String message = "Error in " + LOG_TAG; + Logger.logStackTraceWithMessage(LOG_TAG, message, e); + + TermuxCrashUtils.sendPluginCrashReportNotification(ResultReturner.context, LOG_TAG, + TermuxConstants.TERMUX_API_APP_NAME + " Error", message, e); + if (asyncResult != null) { asyncResult.setResultCode(1); } else if (activity != null) { @@ -209,4 +221,8 @@ public static void returnData(Object context, final Intent intent, final ResultW } } + public static void setContext(Context context) { + ResultReturner.context = context.getApplicationContext(); + } + } From 3c1a6be86ff0768fa8be029267fbe96dd7fbfb7f Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 18 Mar 2022 07:09:30 +0500 Subject: [PATCH 057/142] Changed: Do not hang indefinitely when exceptions are thrown in `TermuxApiReceiver` and `ResultReturner` --- .../com/termux/api/TermuxApiReceiver.java | 3 + .../com/termux/api/util/ResultReturner.java | 68 +++++++++++-------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index 2d7fe5123..53c1eeecb 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -46,6 +46,7 @@ import com.termux.api.apis.WallpaperAPI; import com.termux.api.apis.WifiAPI; import com.termux.api.activities.TermuxApiPermissionActivity; +import com.termux.api.util.ResultReturner; import com.termux.shared.data.IntentUtils; import com.termux.shared.logger.Logger; import com.termux.shared.termux.TermuxConstants; @@ -70,6 +71,8 @@ public void onReceive(Context context, Intent intent) { TermuxCrashUtils.sendPluginCrashReportNotification(context, LOG_TAG, TermuxConstants.TERMUX_API_APP_NAME + " Error", message, e); + + ResultReturner.noteDone(this, intent); } } diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java index 60c9a762a..148e8973d 100644 --- a/app/src/main/java/com/termux/api/util/ResultReturner.java +++ b/app/src/main/java/com/termux/api/util/ResultReturner.java @@ -148,38 +148,41 @@ public static void returnData(Object context, final Intent intent, final ResultW final Activity activity = (Activity) ((context instanceof Activity) ? context : null); final Runnable runnable = () -> { + PrintWriter writer = null; + LocalSocket outputSocket = null; try { final ParcelFileDescriptor[] pfds = { null }; - try (LocalSocket outputSocket = new LocalSocket()) { - String outputSocketAdress = intent.getStringExtra(SOCKET_OUTPUT_EXTRA); - outputSocket.connect(new LocalSocketAddress(outputSocketAdress)); - try (PrintWriter writer = new PrintWriter(outputSocket.getOutputStream())) { - if (resultWriter != null) { - if (resultWriter instanceof BinaryOutput) { - BinaryOutput bout = (BinaryOutput) resultWriter; - bout.setOutput(outputSocket.getOutputStream()); - } - if (resultWriter instanceof WithInput) { - try (LocalSocket inputSocket = new LocalSocket()) { - String inputSocketAdress = intent.getStringExtra(SOCKET_INPUT_EXTRA); - inputSocket.connect(new LocalSocketAddress(inputSocketAdress)); - ((WithInput) resultWriter).setInput(inputSocket.getInputStream()); - resultWriter.writeResult(writer); - } - } else { - resultWriter.writeResult(writer); - } - if(resultWriter instanceof WithAncillaryFd) { - int fd = ((WithAncillaryFd) resultWriter).getFd(); - if (fd >= 0) { - pfds[0] = ParcelFileDescriptor.adoptFd(fd); - FileDescriptor[] fds = { pfds[0].getFileDescriptor() }; - outputSocket.setFileDescriptorsForSend(fds); - } - } + outputSocket = new LocalSocket(); + String outputSocketAdress = intent.getStringExtra(SOCKET_OUTPUT_EXTRA); + Logger.logDebug(LOG_TAG, "Connecting to output socket \"" + outputSocketAdress + "\""); + outputSocket.connect(new LocalSocketAddress(outputSocketAdress)); + writer = new PrintWriter(outputSocket.getOutputStream()); + + if (resultWriter != null) { + if (resultWriter instanceof BinaryOutput) { + BinaryOutput bout = (BinaryOutput) resultWriter; + bout.setOutput(outputSocket.getOutputStream()); + } + if (resultWriter instanceof WithInput) { + try (LocalSocket inputSocket = new LocalSocket()) { + String inputSocketAdress = intent.getStringExtra(SOCKET_INPUT_EXTRA); + inputSocket.connect(new LocalSocketAddress(inputSocketAdress)); + ((WithInput) resultWriter).setInput(inputSocket.getInputStream()); + resultWriter.writeResult(writer); + } + } else { + resultWriter.writeResult(writer); + } + if(resultWriter instanceof WithAncillaryFd) { + int fd = ((WithAncillaryFd) resultWriter).getFd(); + if (fd >= 0) { + pfds[0] = ParcelFileDescriptor.adoptFd(fd); + FileDescriptor[] fds = { pfds[0].getFileDescriptor() }; + outputSocket.setFileDescriptorsForSend(fds); } } } + if(pfds[0] != null) { pfds[0].close(); } @@ -202,6 +205,15 @@ public static void returnData(Object context, final Intent intent, final ResultW activity.setResult(1); } } finally { + try { + if (writer != null) + writer.close(); + if (outputSocket != null) + outputSocket.close(); + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to close", e); + } + try { if (asyncResult != null) { asyncResult.finish(); @@ -209,7 +221,7 @@ public static void returnData(Object context, final Intent intent, final ResultW activity.finish(); } } catch (Exception e) { - Logger.logStackTraceWithMessage(LOG_TAG, "Failed to cleanup", e); + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to finish", e); } } }; From ac967e8a2971cf349b6bec375ac672a785c2c63f Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Tue, 22 Mar 2022 15:19:56 +0500 Subject: [PATCH 058/142] Changed: Rename `PhotoAPI` to `CameraPhotoAPI` --- app/src/main/java/com/termux/api/TermuxApiReceiver.java | 4 ++-- .../termux/api/apis/{PhotoAPI.java => CameraPhotoAPI.java} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename app/src/main/java/com/termux/api/apis/{PhotoAPI.java => CameraPhotoAPI.java} (99%) diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index 53c1eeecb..ba54b865c 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -13,6 +13,7 @@ import com.termux.api.apis.BrightnessAPI; import com.termux.api.apis.CallLogAPI; import com.termux.api.apis.CameraInfoAPI; +import com.termux.api.apis.CameraPhotoAPI; import com.termux.api.apis.ClipboardAPI; import com.termux.api.apis.ContactListAPI; import com.termux.api.apis.DialogAPI; @@ -28,7 +29,6 @@ import com.termux.api.apis.NfcAPI; import com.termux.api.apis.NotificationAPI; import com.termux.api.apis.NotificationListAPI; -import com.termux.api.apis.PhotoAPI; import com.termux.api.apis.SAFAPI; import com.termux.api.apis.SensorAPI; import com.termux.api.apis.ShareAPI; @@ -107,7 +107,7 @@ private void doWork(Context context, Intent intent) { break; case "CameraPhoto": if (TermuxApiPermissionActivity.checkAndRequestPermissions(context, intent, Manifest.permission.CAMERA)) { - PhotoAPI.onReceive(this, context, intent); + CameraPhotoAPI.onReceive(this, context, intent); } break; case "CallLog": diff --git a/app/src/main/java/com/termux/api/apis/PhotoAPI.java b/app/src/main/java/com/termux/api/apis/CameraPhotoAPI.java similarity index 99% rename from app/src/main/java/com/termux/api/apis/PhotoAPI.java rename to app/src/main/java/com/termux/api/apis/CameraPhotoAPI.java index 0235538a2..3db4afc94 100644 --- a/app/src/main/java/com/termux/api/apis/PhotoAPI.java +++ b/app/src/main/java/com/termux/api/apis/CameraPhotoAPI.java @@ -38,9 +38,9 @@ import java.util.List; import java.util.Objects; -public class PhotoAPI { +public class CameraPhotoAPI { - private static final String LOG_TAG = "PhotoAPI"; + private static final String LOG_TAG = "CameraPhotoAPI"; public static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) { Logger.logDebug(LOG_TAG, "onReceive"); From 789957de28e95313ca95a1956cc134745df02caf Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 26 Mar 2022 23:53:00 +0500 Subject: [PATCH 059/142] Changed: Remove left over `replyKey` key from `NotificationAPI` from 4e49c391 and 51e6a4b1 --- app/src/main/java/com/termux/api/apis/NotificationAPI.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/NotificationAPI.java b/app/src/main/java/com/termux/api/apis/NotificationAPI.java index c427fff2f..caee093a3 100644 --- a/app/src/main/java/com/termux/api/apis/NotificationAPI.java +++ b/app/src/main/java/com/termux/api/apis/NotificationAPI.java @@ -357,8 +357,7 @@ private static Intent getMessageReplyIntent(Intent oldIntent, setClassName(TermuxConstants.TERMUX_API_PACKAGE_NAME, TermuxAPIConstants.TERMUX_API_RECEIVER_NAME). putExtra("api_method", "NotificationReply"). putExtra("id", notificationId). - putExtra("action", buttonAction). - putExtra("replyKey", buttonText); + putExtra("action", buttonAction); } @@ -378,7 +377,6 @@ public static void onReceiveReplyToNotification(TermuxApiReceiver termuxApiRecei Context context, Intent intent) { Logger.logDebug(LOG_TAG, "onReceiveReplyToNotification"); - String replyKey = intent.getStringExtra("replyKey"); CharSequence reply = getMessageText(intent); String action = intent.getStringExtra("action"); From c845650f3018b9bdd4aa2c0c5f57a109c2710f0b Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 30 Mar 2022 19:59:08 +0500 Subject: [PATCH 060/142] Changed: Bump termux-shared to ce12b8ad2d Required commits termux/termux-app@ce12b8ad --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index df0efafd8..b41e2f15b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,7 +67,7 @@ dependencies { implementation 'androidx.biometric:biometric:1.2.0-alpha03' implementation 'androidx.media:media:1.4.3' - implementation 'com.termux.termux-app:termux-shared:caa13b7047' + implementation 'com.termux.termux-app:termux-shared:ce12b8ad2d' // Use if below libraries are published locally by termux-app with `./gradlew publishReleasePublicationToMavenLocal` and used with `mavenLocal()`. // If updates are done, republish there and sync project with gradle files here // https://github.com/termux/termux-app/wiki/Termux-Libraries From ba2836ba60083b54dcdb6a3a926777f3f0704829 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 30 Mar 2022 20:16:07 +0500 Subject: [PATCH 061/142] Added: Request disable battery optimizations and grant draw over apps permission Previously, the `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` and `SYSTEM_ALERT_WINDOW` (draw over apps) permissions weren't requested by the `termux-api` app (only `termux-app`), so the options to disable/grant them wasn't shown in android settings. Moreover, `TermuxAPIActivity` have added warnings to request them if they have not been, in addition to vendor specific warnings, including link to https://dontkillmyapp.com. --- app/src/main/AndroidManifest.xml | 2 + .../api/activities/TermuxAPIActivity.java | 92 +++++++++++++++++++ .../java/com/termux/api/util/ViewUtils.java | 33 +++++++ .../main/res/layout/activity_termux_api.xml | 63 ++++++++++++- app/src/main/res/values/strings.xml | 23 +++++ 5 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/termux/api/util/ViewUtils.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f764d50f8..db4308d3f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,9 +26,11 @@ + + diff --git a/app/src/main/java/com/termux/api/activities/TermuxAPIActivity.java b/app/src/main/java/com/termux/api/activities/TermuxAPIActivity.java index 48e6ac32a..2040276d8 100644 --- a/app/src/main/java/com/termux/api/activities/TermuxAPIActivity.java +++ b/app/src/main/java/com/termux/api/activities/TermuxAPIActivity.java @@ -5,14 +5,18 @@ import android.os.Environment; import android.view.Menu; import android.view.MenuItem; +import android.widget.Button; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; +import com.termux.api.util.ViewUtils; import com.termux.shared.activities.ReportActivity; import com.termux.shared.activity.ActivityUtils; import com.termux.shared.activity.media.AppCompatActivityUtils; import com.termux.shared.android.AndroidUtils; +import com.termux.shared.android.PermissionUtils; +import com.termux.shared.data.IntentUtils; import com.termux.shared.file.FileUtils; import com.termux.shared.logger.Logger; import com.termux.shared.models.ReportInfo; @@ -24,6 +28,12 @@ public class TermuxAPIActivity extends AppCompatActivity { + private TextView mBatteryOptimizationNotDisabledWarning; + private TextView mDisplayOverOtherAppsPermissionNotGrantedWarning; + + private Button mDisableBatteryOptimization; + private Button mGrantDisplayOverOtherAppsPermission; + private static final String LOG_TAG = "TermuxAPIActivity"; @Override @@ -44,8 +54,23 @@ protected void onCreate(Bundle savedInstanceState) { pluginInfo.setText(getString(R.string.plugin_info, TermuxConstants.TERMUX_GITHUB_REPO_URL, TermuxConstants.TERMUX_API_GITHUB_REPO_URL, TermuxConstants.TERMUX_API_APT_PACKAGE_NAME, TermuxConstants.TERMUX_API_APT_GITHUB_REPO_URL)); + + mBatteryOptimizationNotDisabledWarning = findViewById(R.id.textview_battery_optimization_not_disabled_warning); + mDisableBatteryOptimization = findViewById(R.id.btn_disable_battery_optimizations); + mDisableBatteryOptimization.setOnClickListener(v -> requestDisableBatteryOptimizations()); + + mDisplayOverOtherAppsPermissionNotGrantedWarning = findViewById(R.id.textview_display_over_other_apps_not_granted_warning); + mGrantDisplayOverOtherAppsPermission = findViewById(R.id.btn_grant_display_over_other_apps_permission); + mGrantDisplayOverOtherAppsPermission.setOnClickListener(v -> requestDisplayOverOtherAppsPermission()); } + @Override + protected void onResume() { + super.onResume(); + + checkIfBatteryOptimizationNotDisabled(); + checkIfDisplayOverOtherAppsPermissionNotGranted(); + } @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -92,6 +117,73 @@ public void run() { }.start(); } + + + private void checkIfBatteryOptimizationNotDisabled() { + if (mBatteryOptimizationNotDisabledWarning == null) return; + + // If battery optimizations not disabled + if (!PermissionUtils.checkIfBatteryOptimizationsDisabled(this)) { + ViewUtils.setWarningTextViewAndButtonState(this, mBatteryOptimizationNotDisabledWarning, + mDisableBatteryOptimization, true, getString(R.string.action_disable_battery_optimizations)); + } else { + ViewUtils.setWarningTextViewAndButtonState(this, mBatteryOptimizationNotDisabledWarning, + mDisableBatteryOptimization, false, getString(R.string.action_already_disabled)); + } + } + + private void requestDisableBatteryOptimizations() { + Logger.logDebug(LOG_TAG, "Requesting to disable battery optimizations"); + PermissionUtils.requestDisableBatteryOptimizations(this, PermissionUtils.REQUEST_DISABLE_BATTERY_OPTIMIZATIONS); + } + + + + private void checkIfDisplayOverOtherAppsPermissionNotGranted() { + if (mDisplayOverOtherAppsPermissionNotGrantedWarning == null) return; + + // If display over other apps permission not granted + if (!PermissionUtils.checkDisplayOverOtherAppsPermission(this)) { + ViewUtils.setWarningTextViewAndButtonState(this, mDisplayOverOtherAppsPermissionNotGrantedWarning, + mGrantDisplayOverOtherAppsPermission, true, getString(R.string.action_grant_display_over_other_apps_permission)); + } else { + ViewUtils.setWarningTextViewAndButtonState(this, mDisplayOverOtherAppsPermissionNotGrantedWarning, + mGrantDisplayOverOtherAppsPermission, false, getString(R.string.action_already_granted)); + } + } + + private void requestDisplayOverOtherAppsPermission() { + Logger.logDebug(LOG_TAG, "Requesting to grant display over other apps permission"); + PermissionUtils.requestDisplayOverOtherAppsPermission(this, PermissionUtils.REQUEST_GRANT_DISPLAY_OVER_OTHER_APPS_PERMISSION); + } + + + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + Logger.logVerbose(LOG_TAG, "onActivityResult: requestCode: " + requestCode + ", resultCode: " + resultCode + ", data: " + IntentUtils.getIntentString(data)); + + switch (requestCode) { + case PermissionUtils.REQUEST_DISABLE_BATTERY_OPTIMIZATIONS: + if(PermissionUtils.checkIfBatteryOptimizationsDisabled(this)) + Logger.logDebug(LOG_TAG, "Battery optimizations disabled by user on request."); + else + Logger.logDebug(LOG_TAG, "Battery optimizations not disabled by user on request."); + break; + case PermissionUtils.REQUEST_GRANT_DISPLAY_OVER_OTHER_APPS_PERMISSION: + if(PermissionUtils.checkDisplayOverOtherAppsPermission(this)) + Logger.logDebug(LOG_TAG, "Display over other apps granted by user on request."); + else + Logger.logDebug(LOG_TAG, "Display over other apps denied by user on request."); + break; + default: + Logger.logError(LOG_TAG, "Unknown request code \"" + requestCode + "\" passed to onRequestPermissionsResult"); + } + } + + + private void openSettings() { ActivityUtils.startActivity(this, new Intent().setClassName(TermuxConstants.TERMUX_PACKAGE_NAME, TermuxConstants.TERMUX_APP.TERMUX_SETTINGS_ACTIVITY_NAME)); } diff --git a/app/src/main/java/com/termux/api/util/ViewUtils.java b/app/src/main/java/com/termux/api/util/ViewUtils.java new file mode 100644 index 000000000..91b8c970b --- /dev/null +++ b/app/src/main/java/com/termux/api/util/ViewUtils.java @@ -0,0 +1,33 @@ +package com.termux.api.util; + +import android.content.Context; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import com.termux.api.R; +import com.termux.shared.theme.ThemeUtils; + +public class ViewUtils { + + public static void setWarningTextViewAndButtonState(@NonNull Context context, + @NonNull TextView textView, @NonNull Button button, + boolean warningState, String text) { + if (warningState) { + textView.setTextColor(ContextCompat.getColor(context, R.color.red_error)); + textView.setLinkTextColor(ContextCompat.getColor(context, R.color.red_error_link)); + button.setEnabled(true); + button.setAlpha(1); + } else { + textView.setTextColor(ThemeUtils.getTextColorPrimary(context)); + textView.setLinkTextColor(ThemeUtils.getTextColorLink(context)); + button.setEnabled(false); + button.setAlpha(0.5f); + } + + button.setText(text); + } + +} diff --git a/app/src/main/res/layout/activity_termux_api.xml b/app/src/main/res/layout/activity_termux_api.xml index eae4cd827..8431ace2a 100644 --- a/app/src/main/res/layout/activity_termux_api.xml +++ b/app/src/main/res/layout/activity_termux_api.xml @@ -1,5 +1,8 @@ - @@ -9,7 +12,6 @@ android:id="@+id/partial_primary_toolbar"/> + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3abdde809..ca2afe1a0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,29 @@ \n\nReports for some crashes may be shown when you restart &TERMUX_APP_NAME; app. + + + Android battery optimizations + should be disabled for the &TERMUX_API_APP_NAME; app so that termux-api script can start + it from the background if its failing to do so. Do not worry, this will not drain battery, + the app currently only runs commands when called from termux-api script. + Check https://developer.android.com/about/versions/oreo/background and + https://developer.android.com/guide/components/foreground-services#background-start-restrictions + for more info. + + \n\nAlso check https://dontkillmyapp.com for info on vendor specific app killers. + Depending on vendor you may need to do things like enable AutoStart, disable DuraSpeed, + enable `Display pop-up windows while running in the background` for the app. + Disable Battery Optimizations + The display over other + apps permission should be granted to &TERMUX_API_APP_NAME; app for starting foreground + activities from background. Check https://developer.android.com/guide/components/activities/background-starts + for more info. + + Grant Draw Over Apps Permission + + Already Granted + Already Disabled Info Settings From 24fbea780f1edb79318885659d82ef748cafcd06 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 23 Apr 2022 00:20:31 +0500 Subject: [PATCH 062/142] Fixed: Fix typo in `CameraPhotoAPI` and `StorageGetAPI` that was getting basename instead of dirname of file paths for parent dir Created in cc186d03 and 950f9be3 --- app/src/main/java/com/termux/api/apis/CameraPhotoAPI.java | 5 +++-- app/src/main/java/com/termux/api/apis/StorageGetAPI.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/termux/api/apis/CameraPhotoAPI.java b/app/src/main/java/com/termux/api/apis/CameraPhotoAPI.java index 3db4afc94..3cc6738a0 100644 --- a/app/src/main/java/com/termux/api/apis/CameraPhotoAPI.java +++ b/app/src/main/java/com/termux/api/apis/CameraPhotoAPI.java @@ -54,9 +54,10 @@ public static void onReceive(TermuxApiReceiver apiReceiver, final Context contex return; } - // Get canonical path of filePath + // Get canonical path of photoFilePath String photoFilePath = TermuxFileUtils.getCanonicalPath(filePath, null, true); - String photoDirPath = FileUtils.getFileBasename(photoFilePath); + String photoDirPath = FileUtils.getFileDirname(photoFilePath); + Logger.logVerbose(LOG_TAG, "photoFilePath=\"" + photoFilePath + "\", photoDirPath=\"" + photoDirPath + "\""); // If workingDirectory is not a directory, or is not readable or writable, then just return // Creation of missing directory and setting of read, write and execute permissions are only done if workingDirectory is diff --git a/app/src/main/java/com/termux/api/apis/StorageGetAPI.java b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java index abfd2c697..2101c8cf1 100644 --- a/app/src/main/java/com/termux/api/apis/StorageGetAPI.java +++ b/app/src/main/java/com/termux/api/apis/StorageGetAPI.java @@ -41,7 +41,8 @@ public static void onReceive(TermuxApiReceiver apiReceiver, final Context contex // Get canonical path of fileExtra String filePath = TermuxFileUtils.getCanonicalPath(fileExtra, null, true); - String fileParentDirPath = FileUtils.getFileBasename(filePath); + String fileParentDirPath = FileUtils.getFileDirname(filePath); + Logger.logVerbose(LOG_TAG, "filePath=\"" + filePath + "\", fileParentDirPath=\"" + fileParentDirPath + "\""); Error error = FileUtils.checkMissingFilePermissions("file parent directory", fileParentDirPath, "rw-", true); if (error != null) { From fab68728e9d77b15d0dec48c65f923c67af37505 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 23 Apr 2022 00:25:07 +0500 Subject: [PATCH 063/142] Fixed: Fix exception for missing `android.nfc.action.TECH_DISCOVERED` meta-data for `NfcAPI$NfcActivity` https://developer.android.com/guide/topics/connectivity/nfc/nfc#tech-disc ``` W/RegisteredComponentCache: Unable to load component info ResolveInfo{eed0b9c com.termux.api/.apis.NfcAPI$NfcActivity m=0x108000} org.xmlpull.v1.XmlPullParserException: No android.nfc.action.TECH_DISCOVERED meta-data at com.android.nfc.RegisteredComponentCache.parseComponentInfo(RegisteredComponentCache.java:186) at com.android.nfc.RegisteredComponentCache.generateComponentsList(RegisteredComponentCache.java:161) at com.android.nfc.RegisteredComponentCache$1.onReceive(RegisteredComponentCache.java:66) at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:1162) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6247) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:872) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:762) at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:107) ``` --- app/src/main/AndroidManifest.xml | 4 ++++ app/src/main/res/xml/nfc_tech_filter.xml | 5 +++++ 2 files changed, 9 insertions(+) create mode 100644 app/src/main/res/xml/nfc_tech_filter.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index db4308d3f..a046ab6f1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -108,6 +108,10 @@ + + + + + android.nfc.tech.Ndef + + From 3b85bc1e484433ed2103ae871aac7764bb8f7cb1 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 23 Apr 2022 00:30:42 +0500 Subject: [PATCH 064/142] Fixed: Fix crash if input or output socket extras are not set `com.termux.api A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 23068 (Thread-4)` --- app/src/main/java/com/termux/api/util/ResultReturner.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java index 148e8973d..ac5a0e182 100644 --- a/app/src/main/java/com/termux/api/util/ResultReturner.java +++ b/app/src/main/java/com/termux/api/util/ResultReturner.java @@ -18,6 +18,7 @@ import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; @@ -154,6 +155,8 @@ public static void returnData(Object context, final Intent intent, final ResultW final ParcelFileDescriptor[] pfds = { null }; outputSocket = new LocalSocket(); String outputSocketAdress = intent.getStringExtra(SOCKET_OUTPUT_EXTRA); + if (outputSocketAdress == null || outputSocketAdress.isEmpty()) + throw new IOException("Missing '" + SOCKET_OUTPUT_EXTRA + "' extra"); Logger.logDebug(LOG_TAG, "Connecting to output socket \"" + outputSocketAdress + "\""); outputSocket.connect(new LocalSocketAddress(outputSocketAdress)); writer = new PrintWriter(outputSocket.getOutputStream()); @@ -166,6 +169,8 @@ public static void returnData(Object context, final Intent intent, final ResultW if (resultWriter instanceof WithInput) { try (LocalSocket inputSocket = new LocalSocket()) { String inputSocketAdress = intent.getStringExtra(SOCKET_INPUT_EXTRA); + if (inputSocketAdress == null || inputSocketAdress.isEmpty()) + throw new IOException("Missing '" + SOCKET_INPUT_EXTRA + "' extra"); inputSocket.connect(new LocalSocketAddress(inputSocketAdress)); ((WithInput) resultWriter).setInput(inputSocket.getInputStream()); resultWriter.writeResult(writer); From c99dc588538fcd52a843dc8e84a27f07c286c698 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 23 Apr 2022 00:32:46 +0500 Subject: [PATCH 065/142] Changed: Catch `Throwable` instead of `Exception` for uncaught exceptions for API calls --- app/src/main/java/com/termux/api/TermuxApiReceiver.java | 6 +++--- app/src/main/java/com/termux/api/util/ResultReturner.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index ba54b865c..3cec7f47b 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -63,14 +63,14 @@ public void onReceive(Context context, Intent intent) { try { doWork(context, intent); - } catch (Exception e) { + } catch (Throwable t) { String message = "Error in " + LOG_TAG; // Make sure never to throw exception from BroadCastReceiver to avoid "process is bad" // behaviour from the Android system. - Logger.logStackTraceWithMessage(LOG_TAG, message, e); + Logger.logStackTraceWithMessage(LOG_TAG, message, t); TermuxCrashUtils.sendPluginCrashReportNotification(context, LOG_TAG, - TermuxConstants.TERMUX_API_APP_NAME + " Error", message, e); + TermuxConstants.TERMUX_API_APP_NAME + " Error", message, t); ResultReturner.noteDone(this, intent); } diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java index ac5a0e182..644a2e4d3 100644 --- a/app/src/main/java/com/termux/api/util/ResultReturner.java +++ b/app/src/main/java/com/termux/api/util/ResultReturner.java @@ -197,12 +197,12 @@ public static void returnData(Object context, final Intent intent, final ResultW } else if (activity != null) { activity.setResult(0); } - } catch (Exception e) { + } catch (Throwable t) { String message = "Error in " + LOG_TAG; - Logger.logStackTraceWithMessage(LOG_TAG, message, e); + Logger.logStackTraceWithMessage(LOG_TAG, message, t); TermuxCrashUtils.sendPluginCrashReportNotification(ResultReturner.context, LOG_TAG, - TermuxConstants.TERMUX_API_APP_NAME + " Error", message, e); + TermuxConstants.TERMUX_API_APP_NAME + " Error", message, t); if (asyncResult != null) { asyncResult.setResultCode(1); From ba8166a685621d0875339dd1a0f09655df43b6c5 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 23 Apr 2022 00:58:29 +0500 Subject: [PATCH 066/142] Changed: Bump termux-shared to 7f7d889dd0 Required commits termux/termux-app@cc981d8a --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b41e2f15b..8a6c11415 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,7 +67,7 @@ dependencies { implementation 'androidx.biometric:biometric:1.2.0-alpha03' implementation 'androidx.media:media:1.4.3' - implementation 'com.termux.termux-app:termux-shared:ce12b8ad2d' + implementation 'com.termux.termux-app:termux-shared:7f7d889dd0' // Use if below libraries are published locally by termux-app with `./gradlew publishReleasePublicationToMavenLocal` and used with `mavenLocal()`. // If updates are done, republish there and sync project with gradle files here // https://github.com/termux/termux-app/wiki/Termux-Libraries From 1f4a3f2c0b9fb2bc991f15879c69f7f8506c5278 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 23 Apr 2022 00:59:26 +0500 Subject: [PATCH 067/142] Changed: Send plugin error notification instead of crash notification for uncaught exceptions for API calls --- app/src/main/java/com/termux/api/TermuxApiReceiver.java | 4 ++-- app/src/main/java/com/termux/api/util/ResultReturner.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java index 3cec7f47b..6efaafe16 100644 --- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java +++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java @@ -50,7 +50,7 @@ import com.termux.shared.data.IntentUtils; import com.termux.shared.logger.Logger; import com.termux.shared.termux.TermuxConstants; -import com.termux.shared.termux.crash.TermuxCrashUtils; +import com.termux.shared.termux.plugins.TermuxPluginUtils; public class TermuxApiReceiver extends BroadcastReceiver { @@ -69,7 +69,7 @@ public void onReceive(Context context, Intent intent) { // behaviour from the Android system. Logger.logStackTraceWithMessage(LOG_TAG, message, t); - TermuxCrashUtils.sendPluginCrashReportNotification(context, LOG_TAG, + TermuxPluginUtils.sendPluginCommandErrorNotification(context, LOG_TAG, TermuxConstants.TERMUX_API_APP_NAME + " Error", message, t); ResultReturner.noteDone(this, intent); diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java index 644a2e4d3..4ae93f097 100644 --- a/app/src/main/java/com/termux/api/util/ResultReturner.java +++ b/app/src/main/java/com/termux/api/util/ResultReturner.java @@ -14,7 +14,7 @@ import com.termux.shared.logger.Logger; import com.termux.shared.termux.TermuxConstants; -import com.termux.shared.termux.crash.TermuxCrashUtils; +import com.termux.shared.termux.plugins.TermuxPluginUtils; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; @@ -201,7 +201,7 @@ public static void returnData(Object context, final Intent intent, final ResultW String message = "Error in " + LOG_TAG; Logger.logStackTraceWithMessage(LOG_TAG, message, t); - TermuxCrashUtils.sendPluginCrashReportNotification(ResultReturner.context, LOG_TAG, + TermuxPluginUtils.sendPluginCommandErrorNotification(ResultReturner.context, LOG_TAG, TermuxConstants.TERMUX_API_APP_NAME + " Error", message, t); if (asyncResult != null) { From b296a0c29eea7f9f9f88eb560e53b088605ad575 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 23 Apr 2022 01:52:45 +0500 Subject: [PATCH 068/142] Changed: Do not add `liblocal-socket.so` to APK added via termux-shared --- app/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8a6c11415..fbe8fb61f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,8 +57,9 @@ android { } packagingOptions { - // Remove terminal-emulator JNI libs added via termux-shared dependency + // Remove terminal-emulator and termux-shared JNI libs added via termux-shared dependency exclude 'lib/*/libtermux.so' + exclude 'lib/*/liblocal-socket.so' } } From df3cb50b2819b5b23fe3dc3b2ef1b90c7d425c83 Mon Sep 17 00:00:00 2001 From: David Kramer Date: Sat, 9 Jul 2022 17:52:58 -0500 Subject: [PATCH 069/142] Fixed: Text not visible when in dark mode for bottom sheet dialog --- app/src/main/res/values/styles.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 908699a04..c2b7fe27d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -15,10 +15,12 @@