1
0
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:
Fredrik Fornwall 2015-07-29 04:19:45 +02:00
parent a69c640be1
commit 9d1a918d03
8 changed files with 460 additions and 64 deletions

View File

@ -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>

View 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());
}
}
}

View 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);
}
}
}

View File

@ -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 + "'");
}
}
}

View 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();
}
});
}
});
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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>