mirror of
https://github.com/danog/termux-api.git
synced 2024-11-30 04:19:20 +01:00
Add PhotoAPI, ShareAPI and ToastAPI
This commit is contained in:
parent
a69c640be1
commit
9d1a918d03
@ -39,6 +39,10 @@
|
||||
<activity android:name="com.termux.api.DialogActivity" android:theme="@android:style/Theme.Material.Light.Dialog.Alert" android:noHistory="true" android:excludeFromRecents="true" android:exported="false"/>
|
||||
<service android:name="com.termux.api.SpeechToTextAPI$SpeechToTextService"/>
|
||||
<service android:name="com.termux.api.TextToSpeechAPI$TextToSpeechService" />
|
||||
<provider android:authorities="com.termux.sharedfiles"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:name="com.termux.api.ShareAPI$ContentProvider" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
187
app/src/main/java/com/termux/api/PhotoAPI.java
Normal file
187
app/src/main/java/com/termux/api/PhotoAPI.java
Normal file
@ -0,0 +1,187 @@
|
||||
package com.termux.api;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.hardware.camera2.CameraCaptureSession;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraDevice;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.camera2.CameraMetadata;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.TotalCaptureResult;
|
||||
import android.hardware.camera2.params.StreamConfigurationMap;
|
||||
import android.media.Image;
|
||||
import android.media.ImageReader;
|
||||
import android.os.Looper;
|
||||
import android.util.Size;
|
||||
import android.view.Surface;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.termux.api.util.ResultReturner;
|
||||
import com.termux.api.util.TermuxApiLogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class PhotoAPI {
|
||||
|
||||
static void onReceive(TermuxApiReceiver apiReceiver, final Context context, Intent intent) {
|
||||
final String filePath = intent.getStringExtra("file");
|
||||
final File outputFile = new File(filePath);
|
||||
final String cameraId = Objects.toString(intent.getStringExtra("camera"), "0");
|
||||
TermuxApiLogger.info("cameraId=" + cameraId + ", filePath=" + outputFile.getAbsolutePath());
|
||||
|
||||
ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultWriter() {
|
||||
@Override
|
||||
public void writeResult(PrintWriter out) throws Exception {
|
||||
takePictureNoPreview(out, context, outputFile, cameraId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void takePictureNoPreview(final PrintWriter out, final Context context, final File outputFile, String cameraId) {
|
||||
try {
|
||||
final CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
||||
CameraCharacteristics characteristics = null;
|
||||
try {
|
||||
characteristics = manager.getCameraCharacteristics(cameraId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
out.println("No such camera: " + cameraId);
|
||||
return;
|
||||
}
|
||||
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
|
||||
// For still image captures, we use the largest available size.
|
||||
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
|
||||
new CompareSizesByArea());
|
||||
final ImageReader mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
|
||||
ImageFormat.JPEG, 2);
|
||||
Looper.prepare();
|
||||
final Looper looper = Looper.myLooper();
|
||||
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
|
||||
@Override
|
||||
public void onImageAvailable(final ImageReader reader) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try (final Image mImage = reader.acquireNextImage()) {
|
||||
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
try (FileOutputStream output = new FileOutputStream(outputFile)) {
|
||||
output.write(bytes);
|
||||
} catch (Exception e) {
|
||||
out.append("Error writing image: " + e.getMessage());
|
||||
TermuxApiLogger.error("Error writing image", e);
|
||||
} finally {
|
||||
looper.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
}, null);
|
||||
|
||||
manager.openCamera(cameraId, new CameraDevice.StateCallback() {
|
||||
@Override
|
||||
public void onOpened(final CameraDevice camera) {
|
||||
try {
|
||||
final CaptureRequest.Builder captureBuilder = camera
|
||||
.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
|
||||
captureBuilder.addTarget(mImageReader.getSurface());
|
||||
|
||||
// Use the same AE and AF modes as the preview.
|
||||
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
|
||||
CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
||||
captureBuilder
|
||||
.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH);
|
||||
|
||||
// Orientation jpeg fix, from the Camera2BasicFragment example:
|
||||
int cameraJpegOrientation;
|
||||
int deviceOrientation = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getOrientation();
|
||||
switch (deviceOrientation) {
|
||||
case Surface.ROTATION_0:
|
||||
cameraJpegOrientation = 90;
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
cameraJpegOrientation = 0;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
cameraJpegOrientation = 270;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
cameraJpegOrientation = 180;
|
||||
break;
|
||||
default:
|
||||
cameraJpegOrientation = 0;
|
||||
}
|
||||
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, cameraJpegOrientation);
|
||||
|
||||
List<Surface> outputSurfaces = Collections.singletonList(mImageReader.getSurface());
|
||||
camera.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
|
||||
@Override
|
||||
public void onConfigured(CameraCaptureSession session) {
|
||||
try {
|
||||
session.stopRepeating();
|
||||
session.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() {
|
||||
@Override
|
||||
public void onCaptureCompleted(CameraCaptureSession completedSession,
|
||||
CaptureRequest request, TotalCaptureResult result) {
|
||||
TermuxApiLogger.info("onCaptureCompleted()");
|
||||
camera.close();
|
||||
}
|
||||
}, null);
|
||||
} catch (Exception e) {
|
||||
out.println("onConfigured() error: " + e.getMessage());
|
||||
TermuxApiLogger.error("onConfigured() error", e);
|
||||
looper.quit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigureFailed(CameraCaptureSession session) {
|
||||
TermuxApiLogger.error("onConfigureFailed() error");
|
||||
looper.quit();
|
||||
}
|
||||
}, null);
|
||||
} catch (Exception e) {
|
||||
TermuxApiLogger.error("in onOpened", e);
|
||||
looper.quit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(CameraDevice camera) {
|
||||
TermuxApiLogger.info("onDisconnected() from camera");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(CameraDevice camera, int error) {
|
||||
TermuxApiLogger.error("Failed opening camera: " + error);
|
||||
looper.quit();
|
||||
}
|
||||
|
||||
}, null);
|
||||
Looper.loop();
|
||||
} catch (Exception e) {
|
||||
TermuxApiLogger.error("Error getting camera", e);
|
||||
}
|
||||
}
|
||||
|
||||
static class CompareSizesByArea implements Comparator<Size> {
|
||||
@Override
|
||||
public int compare(Size lhs, Size rhs) {
|
||||
// We cast here to ensure the multiplications won't overflow
|
||||
return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
158
app/src/main/java/com/termux/api/ShareAPI.java
Normal file
158
app/src/main/java/com/termux/api/ShareAPI.java
Normal file
@ -0,0 +1,158 @@
|
||||
package com.termux.api;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.termux.api.util.ResultReturner;
|
||||
import com.termux.api.util.TermuxApiLogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public class ShareAPI {
|
||||
|
||||
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");
|
||||
final boolean defaultReceiverExtra = intent.getBooleanExtra("default-receiver", false);
|
||||
final String actionExtra = intent.getStringExtra("action");
|
||||
|
||||
String intentAction = null;
|
||||
if (actionExtra == null) {
|
||||
intentAction = Intent.ACTION_VIEW;
|
||||
} else {
|
||||
switch (actionExtra) {
|
||||
case "edit":
|
||||
intentAction = Intent.ACTION_EDIT;
|
||||
break;
|
||||
case "send":
|
||||
intentAction = Intent.ACTION_SEND;
|
||||
break;
|
||||
case "view":
|
||||
intentAction = Intent.ACTION_VIEW;
|
||||
break;
|
||||
default:
|
||||
TermuxApiLogger.error("Invalid action '" + actionExtra + "', using 'view'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
final String finalIntentAction = intentAction;
|
||||
|
||||
if (fileExtra == null) {
|
||||
// Read text to share from stdin.
|
||||
ResultReturner.returnData(apiReceiver, intent, new ResultReturner.WithStringInput() {
|
||||
@Override
|
||||
public void writeResult(PrintWriter out) throws Exception {
|
||||
if (TextUtils.isEmpty(inputString)) {
|
||||
out.println("Error: Nothing to share");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(finalIntentAction);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, inputString);
|
||||
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
if (titleExtra != null) sendIntent.putExtra(Intent.EXTRA_SUBJECT, titleExtra);
|
||||
sendIntent.setType(contentTypeExtra == null ? "text/plain" : contentTypeExtra);
|
||||
|
||||
context.startActivity(Intent.createChooser(sendIntent, context.getResources().getText(R.string.share_file_chooser_title)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Share specified file.
|
||||
ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultWriter() {
|
||||
@Override
|
||||
public void writeResult(PrintWriter out) throws Exception {
|
||||
final File fileToShare = new File(fileExtra);
|
||||
if (!(fileToShare.isFile() && fileToShare.canRead())) {
|
||||
out.println("Not a readable file: '" + fileToShare.getAbsolutePath() + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(finalIntentAction);
|
||||
Uri uriToShare = Uri.withAppendedPath(Uri.parse("content://com.termux.sharedfiles/"), fileExtra);
|
||||
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
String contentTypeToUse;
|
||||
if (contentTypeExtra == null) {
|
||||
String fileName = fileToShare.getName();
|
||||
int lastDotIndex = fileName.lastIndexOf('.');
|
||||
String fileExtension = fileName.substring(lastDotIndex + 1, fileName.length());
|
||||
MimeTypeMap mimeTypes = MimeTypeMap.getSingleton();
|
||||
contentTypeToUse = mimeTypes.getMimeTypeFromExtension(fileExtension);
|
||||
} else {
|
||||
contentTypeToUse = contentTypeExtra;
|
||||
}
|
||||
|
||||
TermuxApiLogger.info("CONTENT: " + contentTypeToUse + ", default=" + defaultReceiverExtra + ", action=" + finalIntentAction);
|
||||
|
||||
if (titleExtra != null) sendIntent.putExtra(Intent.EXTRA_SUBJECT, titleExtra);
|
||||
|
||||
if (Intent.ACTION_SEND.equals(finalIntentAction)) {
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, uriToShare);
|
||||
sendIntent.setType(contentTypeToUse);
|
||||
} else {
|
||||
sendIntent.setDataAndType(uriToShare, contentTypeToUse);
|
||||
}
|
||||
|
||||
if (!defaultReceiverExtra) {
|
||||
sendIntent = Intent.createChooser(sendIntent, context.getResources().getText(R.string.share_file_chooser_title)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
context.startActivity(sendIntent);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class ContentProvider extends android.content.ContentProvider {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
TermuxApiLogger.info("getType(" + uri + ")");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
String fileName = uri.getPath();
|
||||
File file = new File(uri.getPath());
|
||||
TermuxApiLogger.info("...filepath=" + file.getAbsolutePath());
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,64 +1,74 @@
|
||||
package com.termux.api;
|
||||
|
||||
import com.termux.api.util.TermuxApiLogger;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.termux.api.util.TermuxApiLogger;
|
||||
|
||||
public class TermuxApiReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String apiMethod = intent.getStringExtra("api_method");
|
||||
if (apiMethod == null) {
|
||||
TermuxApiLogger.error("Missing 'api_method' extra");
|
||||
return;
|
||||
}
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String apiMethod = intent.getStringExtra("api_method");
|
||||
if (apiMethod == null) {
|
||||
TermuxApiLogger.error("Missing 'api_method' extra");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (apiMethod) {
|
||||
case "BatteryStatus":
|
||||
BatteryStatusAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "CameraInfo":
|
||||
CameraInfoAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Clipboard":
|
||||
ClipboardAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "ContactList":
|
||||
ContactListAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Dialog":
|
||||
context.startActivity(new Intent(context, DialogActivity.class).putExtras(intent.getExtras()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
break;
|
||||
case "Download":
|
||||
DownloadAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Location":
|
||||
LocationAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Notification":
|
||||
NotificationAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "SmsInbox":
|
||||
SmsInboxAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "SmsSend":
|
||||
SmsSendAPI.onReceive(this, intent);
|
||||
break;
|
||||
case "SpeechToText":
|
||||
SpeechToTextAPI.onReceive(context, intent);
|
||||
break;
|
||||
case "TextToSpeech":
|
||||
TextToSpeechAPI.onReceive(context, intent);
|
||||
break;
|
||||
case "Vibrate":
|
||||
VibrateAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
default:
|
||||
TermuxApiLogger.error("Unrecognized 'api_method' extra: '" + apiMethod + "'");
|
||||
}
|
||||
}
|
||||
switch (apiMethod) {
|
||||
case "BatteryStatus":
|
||||
BatteryStatusAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "CameraInfo":
|
||||
CameraInfoAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "CameraPhoto":
|
||||
PhotoAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Clipboard":
|
||||
ClipboardAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "ContactList":
|
||||
ContactListAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Dialog":
|
||||
context.startActivity(new Intent(context, DialogActivity.class).putExtras(intent.getExtras()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
break;
|
||||
case "Download":
|
||||
DownloadAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Location":
|
||||
LocationAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Notification":
|
||||
NotificationAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Share":
|
||||
ShareAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "SmsInbox":
|
||||
SmsInboxAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "SmsSend":
|
||||
SmsSendAPI.onReceive(this, intent);
|
||||
break;
|
||||
case "SpeechToText":
|
||||
SpeechToTextAPI.onReceive(context, intent);
|
||||
break;
|
||||
case "TextToSpeech":
|
||||
TextToSpeechAPI.onReceive(context, intent);
|
||||
break;
|
||||
case "Toast":
|
||||
ToastAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
case "Vibrate":
|
||||
VibrateAPI.onReceive(this, context, intent);
|
||||
break;
|
||||
default:
|
||||
TermuxApiLogger.error("Unrecognized 'api_method' extra: '" + apiMethod + "'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
36
app/src/main/java/com/termux/api/ToastAPI.java
Normal file
36
app/src/main/java/com/termux/api/ToastAPI.java
Normal file
@ -0,0 +1,36 @@
|
||||
package com.termux.api;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.termux.api.util.ResultReturner;
|
||||
import com.termux.api.util.TermuxApiLogger;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public class ToastAPI {
|
||||
|
||||
public static void onReceive(TermuxApiReceiver receiver, final Context context, Intent intent) {
|
||||
final int durationExtra = intent.getBooleanExtra("short", false) ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG;
|
||||
final String gravityExtra = intent.getStringExtra("gravity");
|
||||
|
||||
final Handler handler = new Handler();
|
||||
|
||||
TermuxApiLogger.info("duration=" + durationExtra);
|
||||
ResultReturner.returnData(context, intent, new ResultReturner.WithStringInput() {
|
||||
@Override
|
||||
public void writeResult(PrintWriter out) throws Exception {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast toast = Toast.makeText(context, inputString, durationExtra);
|
||||
toast.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -15,7 +15,7 @@ import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.util.JsonWriter;
|
||||
|
||||
public class ResultReturner {
|
||||
public abstract class ResultReturner {
|
||||
|
||||
/**
|
||||
* An extra intent parameter which specifies a linux abstract namespace socket address where output from the API
|
||||
|
@ -4,18 +4,18 @@ import android.util.Log;
|
||||
|
||||
public class TermuxApiLogger {
|
||||
|
||||
private static final String TAG = "termux-api";
|
||||
private static final String TAG = "termux-api";
|
||||
|
||||
public static void info(String message) {
|
||||
Log.i(TAG, message);
|
||||
}
|
||||
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) {
|
||||
Log.e(TAG, message);
|
||||
}
|
||||
|
||||
public static void error(String message, Exception exception) {
|
||||
Log.e(TAG, message, exception);
|
||||
}
|
||||
public static void error(String message, Exception exception) {
|
||||
Log.e(TAG, message, exception);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Termux:API</string>
|
||||
<string name="share_file_chooser_title">Share with</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user