diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3be2fcbb..32c3957e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -90,6 +90,14 @@ android:name="com.termux.app.TermuxService" android:exported="false" /> + + + + diff --git a/app/src/main/java/com/termux/app/TermuxOpenReceiver.java b/app/src/main/java/com/termux/app/TermuxOpenReceiver.java new file mode 100644 index 00000000..c5a9a2e0 --- /dev/null +++ b/app/src/main/java/com/termux/app/TermuxOpenReceiver.java @@ -0,0 +1,179 @@ +package com.termux.app; + +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.MediaStore; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import com.termux.terminal.EmulatorDebug; + +import java.io.File; +import java.io.FileNotFoundException; + +public class TermuxOpenReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + final Uri data = intent.getData(); + if (data == null) { + Log.e(EmulatorDebug.LOG_TAG, "termux-open: Called without intent data"); + return; + } + + final boolean isExternalUrl = data.getScheme() != null && !data.getScheme().equals("file"); + if (isExternalUrl) { + Intent viewIntent = new Intent(Intent.ACTION_VIEW, data); + try { + context.startActivity(viewIntent); + } catch (ActivityNotFoundException e) { + Log.e(EmulatorDebug.LOG_TAG, "termux-open: No app handles the url " + data); + } + return; + } + + final String filePath = data.getPath(); + final String contentTypeExtra = intent.getStringExtra("content-type"); + final boolean useChooser = intent.getBooleanExtra("chooser", false); + final String actionExtra = intent.getAction(); + + 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: + Log.e(EmulatorDebug.LOG_TAG, "Invalid action '" + actionExtra + "', using 'view'"); + break; + } + } + + final File fileToShare = new File(filePath); + if (!(fileToShare.isFile() && fileToShare.canRead())) { + Log.e(EmulatorDebug.LOG_TAG, "termux-open: Not a readable file: '" + fileToShare.getAbsolutePath() + "'"); + return; + } + + Intent sendIntent = new Intent(); + sendIntent.setAction(intentAction); + Uri uriToShare = Uri.withAppendedPath(Uri.parse("content://com.termux.files/"), filePath); + 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(); + // Lower casing makes it work with e.g. "JPG": + contentTypeToUse = mimeTypes.getMimeTypeFromExtension(fileExtension.toLowerCase()); + if (contentTypeToUse == null) contentTypeToUse = "application/octet-stream"; + } else { + contentTypeToUse = contentTypeExtra; + } + + sendIntent.putExtra(Intent.EXTRA_SUBJECT, fileToShare.getName()); + + if (Intent.ACTION_SEND.equals(intentAction)) { + sendIntent.putExtra(Intent.EXTRA_STREAM, uriToShare); + sendIntent.setType(contentTypeToUse); + } else { + sendIntent.setDataAndType(uriToShare, contentTypeToUse); + } + + if (useChooser) { + sendIntent = Intent.createChooser(sendIntent, null).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) { + File file = new File(uri.getPath()); + String fileName = file.getName(); + + if (projection == null) { + projection = new String[]{ + MediaStore.MediaColumns.DISPLAY_NAME, + MediaStore.MediaColumns.SIZE, + MediaStore.MediaColumns._ID + }; + } + + Object[] row = new Object[projection.length]; + for (int i = 0; i < projection.length; i++) { + String column = projection[i]; + Object value; + switch (column) { + case MediaStore.MediaColumns.DISPLAY_NAME: + value = file.getName(); + break; + case MediaStore.MediaColumns.SIZE: + value = (int) file.length(); + break; + case MediaStore.MediaColumns._ID: + value = 1; + break; + default: + value = null; + } + row[i] = value; + } + + MatrixCursor cursor = new MatrixCursor(projection); + cursor.addRow(row); + return cursor; + } + + @Override + public String getType(Uri 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 { + File file = new File(uri.getPath()); + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + } + } + +}