mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Fixed NPE in DocumentsProvider; Renamed DocumentsProvider to IDEDocumentsProvider
This commit is contained in:
Generated
+17
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<runningDeviceTargetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="RUNNING_DEVICE_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="C:\Users\itsaky01\.android\avd\Pixel_4_API_31.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</runningDeviceTargetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2022-04-01T11:43:50.979181300Z" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -6,6 +6,9 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
||||
@@ -66,8 +69,8 @@
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<provider
|
||||
android:name=".filepicker.DocumentsProvider"
|
||||
android:authorities="com.itsaky.androidide.root"
|
||||
android:name=".filepicker.IDEDocumentsProvider"
|
||||
android:authorities="com.itsaky.androidide.documents"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
@file:Suppress("KDocUnresolvedReference")
|
||||
|
||||
package com.itsaky.androidide.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
|
||||
import android.provider.DocumentsProvider
|
||||
import android.webkit.MimeTypeMap
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A document provider for the Storage Access Framework which exposes the files in the $HOME/
|
||||
* directory to other apps.
|
||||
*
|
||||
*
|
||||
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
|
||||
*
|
||||
*
|
||||
* "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
|
||||
*
|
||||
* @see [TermuxDocumentsProvider](https://github.com/termux/termux-app/blob/master/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java)
|
||||
*/
|
||||
class DocumentsProvider : DocumentsProvider() {
|
||||
override fun queryRoots(projection: Array<String>): Cursor {
|
||||
val result = MatrixCursor(projection)
|
||||
val applicationName = context!!.getString(R.string.app_name)
|
||||
val row = result.newRow()
|
||||
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR))
|
||||
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR))
|
||||
row.add(DocumentsContract.Root.COLUMN_SUMMARY, null)
|
||||
row.add(
|
||||
DocumentsContract.Root.COLUMN_FLAGS,
|
||||
DocumentsContract.Root.FLAG_SUPPORTS_CREATE
|
||||
or DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
|
||||
or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD)
|
||||
row.add(DocumentsContract.Root.COLUMN_TITLE, applicationName)
|
||||
row.add(DocumentsContract.Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES)
|
||||
row.add(DocumentsContract.Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.freeSpace)
|
||||
row.add(DocumentsContract.Root.COLUMN_ICON, R.mipmap.ic_launcher)
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun queryDocument(documentId: String, projection: Array<String>): Cursor {
|
||||
val result = MatrixCursor(projection)
|
||||
includeFile(result, documentId, null)
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun queryChildDocuments(
|
||||
parentDocumentId: String, projection: Array<String>, sortOrder: String): Cursor {
|
||||
val result = MatrixCursor(projection)
|
||||
val parent = getFileForDocId(parentDocumentId)
|
||||
for (file in parent.listFiles()) {
|
||||
includeFile(result, null, file)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun openDocument(
|
||||
documentId: String, mode: String, signal: CancellationSignal?): ParcelFileDescriptor {
|
||||
val file = getFileForDocId(documentId)
|
||||
val accessMode = ParcelFileDescriptor.parseMode(mode)
|
||||
return ParcelFileDescriptor.open(file, accessMode)
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun openDocumentThumbnail(
|
||||
documentId: String, sizeHint: Point, signal: CancellationSignal): AssetFileDescriptor {
|
||||
val file = getFileForDocId(documentId)
|
||||
val pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
|
||||
return AssetFileDescriptor(pfd, 0, file.length())
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun createDocument(parentDocumentId: String, mimeType: String, displayName: String): String {
|
||||
var newFile = File(parentDocumentId, displayName)
|
||||
var noConflictId = 2
|
||||
while (newFile.exists()) {
|
||||
newFile = File(parentDocumentId, displayName + " (" + noConflictId++ + ")")
|
||||
}
|
||||
try {
|
||||
val succeeded: Boolean = if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) {
|
||||
newFile.mkdir()
|
||||
} else {
|
||||
newFile.createNewFile()
|
||||
}
|
||||
|
||||
if (!succeeded) {
|
||||
throw FileNotFoundException(
|
||||
"Failed to create document with id " + newFile.path)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw FileNotFoundException(
|
||||
"Failed to create document with id " + newFile.path)
|
||||
}
|
||||
return newFile.path
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun deleteDocument(documentId: String) {
|
||||
val file = getFileForDocId(documentId)
|
||||
if (!file.delete()) {
|
||||
throw FileNotFoundException("Failed to delete document with id $documentId")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun getDocumentType(documentId: String): String {
|
||||
val file = getFileForDocId(documentId)
|
||||
return getMimeType(file)
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun querySearchDocuments(rootId: String, query: String, projection: Array<String>): Cursor {
|
||||
val result = MatrixCursor(projection)
|
||||
val 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.
|
||||
val pending = LinkedList<File>()
|
||||
pending.add(parent)
|
||||
val maxSearchResults = 50
|
||||
while (!pending.isEmpty() && result.count < maxSearchResults) {
|
||||
val file = pending.removeFirst()
|
||||
// Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g.
|
||||
// search
|
||||
// through the whole SD card).
|
||||
val isInsideHome: Boolean = try {
|
||||
file.canonicalPath.startsWith(Environment.ROOT.absolutePath)
|
||||
} catch (e: IOException) {
|
||||
true
|
||||
}
|
||||
|
||||
if (isInsideHome) {
|
||||
if (file.isDirectory) {
|
||||
Collections.addAll(pending, *file.listFiles())
|
||||
} else {
|
||||
if (file.name.lowercase(Locale.ROOT).contains(query)) {
|
||||
includeFile(result, null, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun isChildDocument(parentDocumentId: String, documentId: String): Boolean {
|
||||
return documentId.startsWith(parentDocumentId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
@Suppress("NAME_SHADOWING")
|
||||
@Throws(FileNotFoundException::class)
|
||||
private fun includeFile(result: MatrixCursor, docId: String?, file: File?) {
|
||||
var docId = docId
|
||||
var file = file
|
||||
if (docId == null) {
|
||||
docId = getDocIdForFile(file)
|
||||
} else {
|
||||
file = getFileForDocId(docId)
|
||||
}
|
||||
var flags = 0
|
||||
if (file!!.isDirectory) {
|
||||
if (file.canWrite()) flags = flags or DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE
|
||||
} else if (file.canWrite()) {
|
||||
flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_WRITE
|
||||
}
|
||||
if (file.parentFile.canWrite()) flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_DELETE
|
||||
val displayName = file.name
|
||||
val mimeType = getMimeType(file)
|
||||
if (mimeType.startsWith("image/")) flags = flags or DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL
|
||||
val row = result.newRow()
|
||||
row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, docId)
|
||||
row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, displayName)
|
||||
row.add(DocumentsContract.Document.COLUMN_SIZE, file.length())
|
||||
row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, mimeType)
|
||||
row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, file.lastModified())
|
||||
row.add(DocumentsContract.Document.COLUMN_FLAGS, flags)
|
||||
row.add(DocumentsContract.Document.COLUMN_ICON, R.mipmap.ic_launcher)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ALL_MIME_TYPES = "*/*"
|
||||
private val BASE_DIR = Environment.ROOT
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* The reverse of @{link #getFileForDocId}.
|
||||
*/
|
||||
private fun getDocIdForFile(file: File?): String {
|
||||
return file!!.absolutePath
|
||||
}
|
||||
|
||||
/** Get the file given a document id (the reverse of [.getDocIdForFile]). */
|
||||
@Throws(FileNotFoundException::class)
|
||||
private fun getFileForDocId(docId: String): File {
|
||||
val f = File(docId)
|
||||
if (!f.exists()) throw FileNotFoundException(f.absolutePath + " not found")
|
||||
return f
|
||||
}
|
||||
|
||||
private fun getMimeType(file: File?): String {
|
||||
return if (file!!.isDirectory) {
|
||||
DocumentsContract.Document.MIME_TYPE_DIR
|
||||
} else {
|
||||
val name = file.name
|
||||
val lastDot = name.lastIndexOf('.')
|
||||
if (lastDot >= 0) {
|
||||
val extension = name.substring(lastDot + 1).lowercase(Locale.ROOT)
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
if (mime != null) return mime
|
||||
}
|
||||
"application/octet-stream"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.filepicker;
|
||||
|
||||
import static com.itsaky.androidide.utils.Logger.instance;
|
||||
|
||||
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 androidx.annotation.NonNull;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
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/
|
||||
* directory 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
|
||||
*
|
||||
* @see <a
|
||||
* href="https://github.com/termux/termux-app/blob/master/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java">TermuxDocumentsProvider</a>
|
||||
*/
|
||||
public class IDEDocumentsProvider extends DocumentsProvider {
|
||||
|
||||
private static final String ALL_MIME_TYPES = "*/*";
|
||||
private static final Logger LOG = instance("IDEDocumentsProvider");
|
||||
private static final File BASE_DIR = getBaseDir();
|
||||
// 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
|
||||
};
|
||||
|
||||
@NonNull
|
||||
private static File getBaseDir() {
|
||||
if (Environment.HOME != null) {
|
||||
return Environment.HOME;
|
||||
}
|
||||
|
||||
return new File(Environment.DEFAULT_HOME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).toLowerCase();
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) return mime;
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
final String applicationName = getContext().getString(R.string.app_name);
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
LOG.debug("queryRoots() before all add");
|
||||
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 1");
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 2");
|
||||
row.add(Root.COLUMN_SUMMARY, null);
|
||||
LOG.debug("queryRoots() before all add, 3");
|
||||
row.add(
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.FLAG_SUPPORTS_CREATE
|
||||
| Root.FLAG_SUPPORTS_SEARCH
|
||||
| Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
LOG.debug("queryRoots() before all add, 4");
|
||||
row.add(Root.COLUMN_TITLE, applicationName);
|
||||
LOG.debug("queryRoots() before all add, 5");
|
||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||
LOG.debug("queryRoots() before all add, 6");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||
LOG.debug("queryRoots() before all add, 7");
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
LOG.debug("queryRoots() before all add");
|
||||
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()) {
|
||||
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 String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||
throws FileNotFoundException {
|
||||
File newFile = new File(parentDocumentId, displayName);
|
||||
int noConflictId = 2;
|
||||
while (newFile.exists()) {
|
||||
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
|
||||
}
|
||||
try {
|
||||
boolean succeeded;
|
||||
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
succeeded = newFile.mkdir();
|
||||
} else {
|
||||
succeeded = newFile.createNewFile();
|
||||
}
|
||||
if (!succeeded) {
|
||||
throw new FileNotFoundException(
|
||||
"Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException(
|
||||
"Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
return newFile.getPath();
|
||||
}
|
||||
|
||||
@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 directories outside the $HOME directory linked with symlinks (to avoid e.g.
|
||||
// search
|
||||
// through the whole SD card).
|
||||
boolean isInsideHome;
|
||||
try {
|
||||
isInsideHome =
|
||||
file.getCanonicalPath().startsWith(Environment.HOME.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
isInsideHome = true;
|
||||
}
|
||||
if (isInsideHome) {
|
||||
if (file.isDirectory()) {
|
||||
Collections.addAll(pending, file.listFiles());
|
||||
} else {
|
||||
if (file.getName().toLowerCase().contains(query)) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
} else if (file.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||
}
|
||||
if (file.getParentFile().canWrite()) flags |= 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.mipmap.ic_launcher);
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,11 @@ import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.ChecksSdkIntAtLeast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.multidex.MultiDexApplication;
|
||||
|
||||
import com.blankj.utilcode.util.ThrowableUtils;
|
||||
import com.itsaky.androidide.common.R;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
@@ -41,11 +39,9 @@ import com.itsaky.androidide.utils.FileUtil;
|
||||
import com.itsaky.androidide.utils.JavaCharacter;
|
||||
import com.itsaky.androidide.utils.StudioUtils;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
public abstract class BaseApplication extends MultiDexApplication {
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.androidide.managers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.ResourceUtils;
|
||||
@@ -26,11 +25,9 @@ import com.blankj.utilcode.util.ZipUtils;
|
||||
import com.itsaky.androidide.app.BaseApplication;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
public class ToolsManager {
|
||||
|
||||
|
||||
@@ -36,10 +36,10 @@ public final class Environment {
|
||||
public static final Map<String, String> IDE_PROPS = new HashMap<>();
|
||||
public static final Map<String, String> ENV_VARS = new HashMap<>();
|
||||
public static final String PROJECTS_FOLDER = "AndroidIDEProjects";
|
||||
private static final String DEFAULT_JAVA_HOME =
|
||||
"/data/data/com.itsaky.androidide/files/framework/jdk";
|
||||
private static final String DEFAULT_ANDROID_HOME =
|
||||
"/data/data/com.itsaky.androidide/files/framework/android-sdk";
|
||||
public static final String DEFAULT_ROOT = "/data/data/com.itsaky.androidide/files";
|
||||
public static final String DEFAULT_HOME = DEFAULT_ROOT + "/framework";
|
||||
private static final String DEFAULT_JAVA_HOME = DEFAULT_HOME + "/jdk";
|
||||
private static final String DEFAULT_ANDROID_HOME = DEFAULT_HOME + "/android-sdk";
|
||||
private static final Logger LOG = Logger.instance("Environment");
|
||||
private static final List<String> blacklist = new ArrayList<>();
|
||||
public static File ROOT;
|
||||
|
||||
Reference in New Issue
Block a user