Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
746dc750df | ||
|
|
7db1f6c5a1 | ||
|
|
b7f3fdf528 | ||
|
|
6e7f777d04 | ||
|
|
fc15bd2355 | ||
|
|
a87cbdd70c | ||
|
|
fb7f7d249e | ||
|
|
026d0b495e | ||
|
|
533fa60516 |
@@ -5,7 +5,7 @@ android {
|
|||||||
buildToolsVersion "23.0.2"
|
buildToolsVersion "23.0.2"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.android.support:support-annotations:23.1.1'
|
compile 'com.android.support:support-annotations:23.3.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -18,8 +18,8 @@ android {
|
|||||||
applicationId "com.termux"
|
applicationId "com.termux"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
versionCode 33
|
versionCode 34
|
||||||
versionName "0.33"
|
versionName "0.34"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -73,30 +73,16 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<provider
|
||||||
android:name="com.termux.filepicker.TermuxFilePickerActivity"
|
android:name=".filepicker.TermuxDocumentsProvider"
|
||||||
android:label="@string/application_name"
|
android:authorities="com.termux.documents"
|
||||||
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
|
|
||||||
android:noHistory="true">
|
|
||||||
<intent-filter>
|
|
||||||
<!--
|
|
||||||
http://stackoverflow.com/questions/6486716/using-intent-action-pick-for-specific-path
|
|
||||||
"That said, you should consider ACTION_PICK deprecated. The modern action is ACTION_GET_CONTENT
|
|
||||||
which is much better supported; you will find support of ACTION_PICK spotty and inconsistent.
|
|
||||||
Unfortunately ACTION_GET_CONTENT also does not let you specify a directory."
|
|
||||||
-->
|
|
||||||
<action android:name="android.intent.action.GET_CONTENT" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.OPENABLE" />
|
|
||||||
<data android:mimeType="*/*" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<provider android:authorities="com.termux.filepicker.provider"
|
|
||||||
android:readPermission="com.termux.filepicker.READ"
|
|
||||||
android:exported="true"
|
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:name="com.termux.filepicker.TermuxFilePickerProvider" />
|
android:exported="true"
|
||||||
|
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||||
|
</intent-filter>
|
||||||
|
</provider>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="com.termux.app.TermuxService"
|
android:name="com.termux.app.TermuxService"
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
@@ -34,7 +33,6 @@ import android.text.SpannableString;
|
|||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
@@ -391,6 +389,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
case TermuxPreferences.BELL_VIBRATE:
|
case TermuxPreferences.BELL_VIBRATE:
|
||||||
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
||||||
break;
|
break;
|
||||||
|
case TermuxPreferences.BELL_IGNORE:
|
||||||
|
// Ignore the bell character.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package com.termux.filepicker;
|
||||||
|
|
||||||
|
import android.content.res.AssetFileDescriptor;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.os.CancellationSignal;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.DocumentsContract.Document;
|
||||||
|
import android.provider.DocumentsContract.Root;
|
||||||
|
import android.provider.DocumentsProvider;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import com.termux.R;
|
||||||
|
import com.termux.app.TermuxService;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A document provider for the Storage Access Framework which exposes the files in the
|
||||||
|
* $HOME/ folder to other apps.
|
||||||
|
* <p>
|
||||||
|
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
|
||||||
|
* <p>
|
||||||
|
* "A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you
|
||||||
|
* support both of them simultaneously, your app will appear twice in the system picker UI,
|
||||||
|
* offering two different ways of accessing your stored data. This would be confusing for users."
|
||||||
|
* - http://developer.android.com/guide/topics/providers/document-provider.html#43
|
||||||
|
*/
|
||||||
|
public class TermuxDocumentsProvider extends DocumentsProvider {
|
||||||
|
|
||||||
|
private static final String ALL_MIME_TYPES = "*/*";
|
||||||
|
|
||||||
|
private static final File BASE_DIR = new File(TermuxService.HOME_PATH);
|
||||||
|
|
||||||
|
|
||||||
|
// The default columns to return information about a root if no specific
|
||||||
|
// columns are requested in a query.
|
||||||
|
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
|
||||||
|
Root.COLUMN_ROOT_ID,
|
||||||
|
Root.COLUMN_MIME_TYPES,
|
||||||
|
Root.COLUMN_FLAGS,
|
||||||
|
Root.COLUMN_ICON,
|
||||||
|
Root.COLUMN_TITLE,
|
||||||
|
Root.COLUMN_SUMMARY,
|
||||||
|
Root.COLUMN_DOCUMENT_ID,
|
||||||
|
Root.COLUMN_AVAILABLE_BYTES
|
||||||
|
};
|
||||||
|
|
||||||
|
// The default columns to return information about a document if no specific
|
||||||
|
// columns are requested in a query.
|
||||||
|
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
|
||||||
|
Document.COLUMN_DOCUMENT_ID,
|
||||||
|
Document.COLUMN_MIME_TYPE,
|
||||||
|
Document.COLUMN_DISPLAY_NAME,
|
||||||
|
Document.COLUMN_LAST_MODIFIED,
|
||||||
|
Document.COLUMN_FLAGS,
|
||||||
|
Document.COLUMN_SIZE
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||||
|
@SuppressWarnings("ConstantConditions") final String applicationName = getContext().getString(R.string.application_name);
|
||||||
|
|
||||||
|
final MatrixCursor.RowBuilder row = result.newRow();
|
||||||
|
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||||
|
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||||
|
row.add(Root.COLUMN_SUMMARY, null);
|
||||||
|
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH);
|
||||||
|
row.add(Root.COLUMN_TITLE, applicationName);
|
||||||
|
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||||
|
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||||
|
row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||||
|
includeFile(result, documentId, null);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||||
|
final File parent = getFileForDocId(parentDocumentId);
|
||||||
|
for (File file : parent.listFiles()) {
|
||||||
|
if (!file.getName().startsWith(".")) {
|
||||||
|
includeFile(result, null, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
|
||||||
|
final File file = getFileForDocId(documentId);
|
||||||
|
final int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||||
|
return ParcelFileDescriptor.open(file, accessMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||||
|
final File file = getFileForDocId(documentId);
|
||||||
|
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
|
return new AssetFileDescriptor(pfd, 0, file.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||||
|
File file = getFileForDocId(documentId);
|
||||||
|
if (!file.delete()) {
|
||||||
|
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||||
|
File file = getFileForDocId(documentId);
|
||||||
|
return getMimeType(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||||
|
final File parent = getFileForDocId(rootId);
|
||||||
|
|
||||||
|
// This example implementation searches file names for the query and doesn't rank search
|
||||||
|
// results, so we can stop as soon as we find a sufficient number of matches. Other
|
||||||
|
// implementations might rank results and use other data about files, rather than the file
|
||||||
|
// name, to produce a match.
|
||||||
|
final LinkedList<File> pending = new LinkedList<>();
|
||||||
|
pending.add(parent);
|
||||||
|
|
||||||
|
final int MAX_SEARCH_RESULTS = 50;
|
||||||
|
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
|
||||||
|
final File file = pending.removeFirst();
|
||||||
|
// Avoid folders outside the $HOME folders linked in to symlinks (to avoid e.g. search
|
||||||
|
// through the whole SD card).
|
||||||
|
boolean isInsideHome;
|
||||||
|
try {
|
||||||
|
isInsideHome = file.getCanonicalPath().startsWith(TermuxService.HOME_PATH);
|
||||||
|
} catch (IOException e) {
|
||||||
|
isInsideHome = true;
|
||||||
|
}
|
||||||
|
final boolean isHidden = file.getName().startsWith(".");
|
||||||
|
if (isInsideHome && !isHidden) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
Collections.addAll(pending, file.listFiles());
|
||||||
|
} else {
|
||||||
|
if (file.getName().toLowerCase().contains(query)) {
|
||||||
|
includeFile(result, null, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the document id given a file. This document id must be consistent across time as other
|
||||||
|
* applications may save the ID and use it to reference documents later.
|
||||||
|
* <p>
|
||||||
|
* The reverse of @{link #getFileForDocId}.
|
||||||
|
*/
|
||||||
|
private static String getDocIdForFile(File file) {
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}).
|
||||||
|
*/
|
||||||
|
private static File getFileForDocId(String docId) throws FileNotFoundException {
|
||||||
|
final File f = new File(docId);
|
||||||
|
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMimeType(File file) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
return Document.MIME_TYPE_DIR;
|
||||||
|
} else {
|
||||||
|
final String name = file.getName();
|
||||||
|
final int lastDot = name.lastIndexOf('.');
|
||||||
|
if (lastDot >= 0) {
|
||||||
|
final String extension = name.substring(lastDot + 1);
|
||||||
|
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||||
|
if (mime != null) return mime;
|
||||||
|
}
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a representation of a file to a cursor.
|
||||||
|
*
|
||||||
|
* @param result the cursor to modify
|
||||||
|
* @param docId the document ID representing the desired file (may be null if given file)
|
||||||
|
* @param file the File object representing the desired file (may be null if given docID)
|
||||||
|
*/
|
||||||
|
private void includeFile(MatrixCursor result, String docId, File file)
|
||||||
|
throws FileNotFoundException {
|
||||||
|
if (docId == null) {
|
||||||
|
docId = getDocIdForFile(file);
|
||||||
|
} else {
|
||||||
|
file = getFileForDocId(docId);
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags = 0;
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
if (file.isDirectory() && file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||||
|
} else if (file.canWrite()) {
|
||||||
|
flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String displayName = file.getName();
|
||||||
|
final String mimeType = getMimeType(file);
|
||||||
|
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||||
|
|
||||||
|
final MatrixCursor.RowBuilder row = result.newRow();
|
||||||
|
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||||
|
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||||
|
row.add(Document.COLUMN_SIZE, file.length());
|
||||||
|
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||||
|
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||||
|
row.add(Document.COLUMN_FLAGS, flags);
|
||||||
|
row.add(Document.COLUMN_ICON, R.drawable.ic_launcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package com.termux.filepicker;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.ListActivity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
|
|
||||||
import com.termux.R;
|
|
||||||
import com.termux.app.TermuxService;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/** Activity allowing picking files from the $HOME folder. */
|
|
||||||
public class TermuxFilePickerActivity extends ListActivity {
|
|
||||||
|
|
||||||
private File mCurrentDirectory;
|
|
||||||
private final List<File> mFiles = new ArrayList<>();
|
|
||||||
private final List<String> mFileNames = new ArrayList<>();
|
|
||||||
private ArrayAdapter mAdapter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.file_picker);
|
|
||||||
|
|
||||||
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mFileNames);
|
|
||||||
|
|
||||||
enterDirectory(new File(TermuxService.HOME_PATH));
|
|
||||||
setListAdapter(mAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
if (id == android.R.id.home) {
|
|
||||||
enterDirectory(mCurrentDirectory.getParentFile());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
|
||||||
super.onListItemClick(l, v, position, id);
|
|
||||||
File requestFile = mFiles.get(position);
|
|
||||||
if (requestFile.isDirectory()) {
|
|
||||||
enterDirectory(requestFile);
|
|
||||||
} else {
|
|
||||||
Uri returnUri = Uri.withAppendedPath(Uri.parse("content://com.termux.filepicker.provider/"), requestFile.getAbsolutePath());
|
|
||||||
Intent returnIntent = new Intent().setData(returnUri);
|
|
||||||
returnIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
setResult(Activity.RESULT_OK, returnIntent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void enterDirectory(File directory) {
|
|
||||||
getActionBar().setDisplayHomeAsUpEnabled(!directory.getAbsolutePath().equals(TermuxService.HOME_PATH));
|
|
||||||
|
|
||||||
String title = directory.getAbsolutePath() + "/";
|
|
||||||
if (title.startsWith(TermuxService.HOME_PATH)) {
|
|
||||||
title = "~" + title.substring(TermuxService.HOME_PATH.length(), title.length());
|
|
||||||
}
|
|
||||||
setTitle(title);
|
|
||||||
|
|
||||||
mCurrentDirectory = directory;
|
|
||||||
mFiles.clear();
|
|
||||||
mFileNames.clear();
|
|
||||||
mFiles.addAll(Arrays.asList(mCurrentDirectory.listFiles()));
|
|
||||||
|
|
||||||
Collections.sort(mFiles, new Comparator<File>() {
|
|
||||||
@Override
|
|
||||||
public int compare(File f1, File f2) {
|
|
||||||
final String n1 = f1.getName();
|
|
||||||
final String n2 = f2.getName();
|
|
||||||
// Display dot folders last:
|
|
||||||
if (n1.startsWith(".") && !n2.startsWith(".")) {
|
|
||||||
return 1;
|
|
||||||
} else if (n2.startsWith(".") && !n1.startsWith(".")) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return n1.compareToIgnoreCase(n2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (File file : mFiles) {
|
|
||||||
mFileNames.add(file.getName() + (file.isDirectory() ? "/" : ""));
|
|
||||||
}
|
|
||||||
mAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package com.termux.filepicker;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.ContentProvider;
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.webkit.MimeTypeMap;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
|
|
||||||
/** Provider of files content uris picked from {@link com.termux.filepicker.TermuxFilePickerActivity}. */
|
|
||||||
public class TermuxFilePickerProvider extends ContentProvider {
|
|
||||||
@Override
|
|
||||||
public boolean onCreate() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getType(@NonNull Uri uri) {
|
|
||||||
String contentType = null;
|
|
||||||
String path = uri.getPath();
|
|
||||||
int lastDotIndex = path.lastIndexOf('.');
|
|
||||||
String possibleFileExtension = path.substring(lastDotIndex + 1, path.length());
|
|
||||||
if (possibleFileExtension.contains("/")) {
|
|
||||||
// The dot was in the path, so not a file extension.
|
|
||||||
} else {
|
|
||||||
MimeTypeMap mimeTypes = MimeTypeMap.getSingleton();
|
|
||||||
// Lower casing makes it work with e.g. "JPG":
|
|
||||||
contentType = mimeTypes.getMimeTypeFromExtension(possibleFileExtension.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contentType == null) contentType = "application/octet-stream";
|
|
||||||
return contentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri insert(@NonNull Uri uri, ContentValues values) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
|
||||||
File file = new File(uri.getPath());
|
|
||||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -133,6 +133,7 @@ public class TermuxFileReceiverActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do this for the user if necessary:
|
// Do this for the user if necessary:
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
editorProgramFile.setExecutable(true);
|
editorProgramFile.setExecutable(true);
|
||||||
|
|
||||||
final Uri scriptUri = new Uri.Builder().scheme("file").path(EDITOR_PROGRAM).build();
|
final Uri scriptUri = new Uri.Builder().scheme("file").path(EDITOR_PROGRAM).build();
|
||||||
@@ -201,6 +202,7 @@ public class TermuxFileReceiverActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do this for the user if necessary:
|
// Do this for the user if necessary:
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
urlOpenerProgramFile.setExecutable(true);
|
urlOpenerProgramFile.setExecutable(true);
|
||||||
|
|
||||||
final Uri urlOpenerProgramUri = new Uri.Builder().scheme("file").path(URL_OPENER_PROGRAM).build();
|
final Uri urlOpenerProgramUri = new Uri.Builder().scheme("file").path(URL_OPENER_PROGRAM).build();
|
||||||
|
|||||||
@@ -652,52 +652,55 @@ public final class TerminalView extends View {
|
|||||||
resultingKeyCode = KeyEvent.KEYCODE_F12;
|
resultingKeyCode = KeyEvent.KEYCODE_F12;
|
||||||
}
|
}
|
||||||
} else if (mVirtualFnKeyDown) {
|
} else if (mVirtualFnKeyDown) {
|
||||||
if (codePoint == 'w' || codePoint == 'W') {
|
int lowerCase = Character.toLowerCase(codePoint);
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP;
|
switch (lowerCase) {
|
||||||
} else if (codePoint == 'a' || codePoint == 'A') {
|
// Arrow keys.
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT;
|
case 'w': resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP; break;
|
||||||
} else if (codePoint == 's' || codePoint == 'S') {
|
case 'a': resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT; break;
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN;
|
case 's': resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN; break;
|
||||||
} else if (codePoint == 'd' || codePoint == 'D') {
|
case 'd': resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT; break;
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
|
|
||||||
} else if (codePoint == 'p' || codePoint == 'P') {
|
// Page up and down.
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP;
|
case 'p': resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP; break;
|
||||||
} else if (codePoint == 'n' || codePoint == 'N') {
|
case 'n': resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN; break;
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN;
|
|
||||||
} else if (codePoint == 't' || codePoint == 'T') {
|
// Some special keys:
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_TAB;
|
case 't': resultingKeyCode = KeyEvent.KEYCODE_TAB; break;
|
||||||
} else if (codePoint == 'l' || codePoint == 'L') {
|
case 'i': resultingKeyCode = KeyEvent.KEYCODE_INSERT; break;
|
||||||
codePoint = '|';
|
case 'h': resultingKeyCode = KeyEvent.KEYCODE_MOVE_HOME; break;
|
||||||
} else if (codePoint == 'u' || codePoint == 'U') {
|
|
||||||
codePoint = '_';
|
// Special characters to input.
|
||||||
} else if (codePoint == 'e' || codePoint == 'E') {
|
case 'u': codePoint = '_'; break;
|
||||||
codePoint = 27; // ^[ (Esc)
|
case 'l': codePoint = '|'; break;
|
||||||
} else if (codePoint == '.') {
|
|
||||||
codePoint = 28; // ^\
|
// Function keys.
|
||||||
} else if (codePoint > '0' && codePoint <= '9') {
|
case '1': case '2': case '3':
|
||||||
// F1-F9
|
case '4': case '5': case '6':
|
||||||
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
|
case '7': case '8': case '9':
|
||||||
} else if (codePoint == '0') {
|
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_F10;
|
break;
|
||||||
} else if (codePoint == 'i' || codePoint == 'I') {
|
case '0':
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
|
resultingKeyCode = KeyEvent.KEYCODE_F10;
|
||||||
} else if (codePoint == 'x' || codePoint == 'X') {
|
break;
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_FORWARD_DEL;
|
|
||||||
} else if (codePoint == 'h' || codePoint == 'H') {
|
// Other special keys.
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_MOVE_HOME;
|
case 'e': codePoint = /*Escape*/ 27; break;
|
||||||
} else if (codePoint == 'f' || codePoint == 'F') {
|
case '.': codePoint = /*^.*/ 28; break;
|
||||||
// As left alt+f, jumping forward in readline:
|
|
||||||
codePoint = 'f';
|
case 'b': // alt+b, jumping backward in readline.
|
||||||
leftAltDownFromEvent = true;
|
case 'f': // alf+f, jumping forward in readline.
|
||||||
} else if (codePoint == 'b' || codePoint == 'B') {
|
case 'x': // alt+x, common in emacs.
|
||||||
// As left alt+b, jumping forward in readline:
|
codePoint = lowerCase;
|
||||||
codePoint = 'b';
|
leftAltDownFromEvent = true;
|
||||||
leftAltDownFromEvent = true;
|
break;
|
||||||
} else if (codePoint == 'v' || codePoint == 'V') {
|
|
||||||
codePoint = -1;
|
// Volume control.
|
||||||
AudioManager audio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
|
case 'v':
|
||||||
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI);
|
codePoint = -1;
|
||||||
}
|
AudioManager audio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codePoint > -1) {
|
if (codePoint > -1) {
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingLeft="8dp"
|
|
||||||
android:paddingRight="8dp">
|
|
||||||
|
|
||||||
<ListView android:id="@android:id/list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:drawSelectorOnTop="false"/>
|
|
||||||
|
|
||||||
<TextView android:id="@android:id/empty"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:text="@string/empty_folder"/>
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -53,8 +53,6 @@
|
|||||||
<string name="notification_action_wakelock">Wake</string>
|
<string name="notification_action_wakelock">Wake</string>
|
||||||
<string name="notification_action_wifilock">Wifi</string>
|
<string name="notification_action_wifilock">Wifi</string>
|
||||||
|
|
||||||
<string name="empty_folder">Empty folder.</string>
|
|
||||||
|
|
||||||
<string name="file_received_title">Save file in ~/downloads/</string>
|
<string name="file_received_title">Save file in ~/downloads/</string>
|
||||||
<string name="file_received_edit_button">Edit</string>
|
<string name="file_received_edit_button">Edit</string>
|
||||||
<string name="file_received_open_folder_button">Open folder</string>
|
<string name="file_received_open_folder_button">Open folder</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user