Extend DocumentsContract search to accept mime types
1. Add the key of query arguments and match method in DocumentsContract. 2. Implement new querySearchDocuments method in DocumentsProvider, ExternalStoragProvider and FileSystemProvider. Bug: 111786939 Test: Manual Test Change-Id: I04e9f2be971f10ac1e9584a3486c948aaddea0a4
This commit is contained in:
154
core/java/android/content/MimeTypeFilter.java
Normal file
154
core/java/android/content/MimeTypeFilter.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.content;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Provides utility methods for matching MIME type filters used in ContentProvider.
|
||||
*
|
||||
* <p>Wildcards are allowed only instead of the entire type or subtype with a tree prefix.
|
||||
* Eg. image\/*, *\/* is a valid filter and will match image/jpeg, but image/j* is invalid and
|
||||
* it will not match image/jpeg. Suffixes and parameters are not supported, and they are treated
|
||||
* as part of the subtype during matching. Neither type nor subtype can be empty.
|
||||
*
|
||||
* <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike the formal
|
||||
* RFC definitions. As a result, you should always write these elements with lower case letters,
|
||||
* or use {@link android.content.Intent#normalizeMimeType} to ensure that they are converted to
|
||||
* lower case.</em>
|
||||
*
|
||||
* <p>MIME types can be null or ill-formatted. In such case they won't match anything.
|
||||
*
|
||||
* <p>MIME type filters must be correctly formatted, or an exception will be thrown.
|
||||
* Copied from support library.
|
||||
* {@hide}
|
||||
*/
|
||||
public final class MimeTypeFilter {
|
||||
|
||||
private MimeTypeFilter() {
|
||||
}
|
||||
|
||||
private static boolean mimeTypeAgainstFilter(
|
||||
@NonNull String[] mimeTypeParts, @NonNull String[] filterParts) {
|
||||
if (filterParts.length != 2) {
|
||||
throw new IllegalArgumentException(
|
||||
"Ill-formatted MIME type filter. Must be type/subtype.");
|
||||
}
|
||||
if (filterParts[0].isEmpty() || filterParts[1].isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Ill-formatted MIME type filter. Type or subtype empty.");
|
||||
}
|
||||
if (mimeTypeParts.length != 2) {
|
||||
return false;
|
||||
}
|
||||
if (!"*".equals(filterParts[0])
|
||||
&& !filterParts[0].equals(mimeTypeParts[0])) {
|
||||
return false;
|
||||
}
|
||||
if (!"*".equals(filterParts[1])
|
||||
&& !filterParts[1].equals(mimeTypeParts[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches one nullable MIME type against one MIME type filter.
|
||||
* @return True if the {@code mimeType} matches the {@code filter}.
|
||||
*/
|
||||
public static boolean matches(@Nullable String mimeType, @NonNull String filter) {
|
||||
if (mimeType == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String[] mimeTypeParts = mimeType.split("/");
|
||||
final String[] filterParts = filter.split("/");
|
||||
|
||||
return mimeTypeAgainstFilter(mimeTypeParts, filterParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches one nullable MIME type against an array of MIME type filters.
|
||||
* @return The first matching filter, or null if nothing matches.
|
||||
*/
|
||||
@Nullable
|
||||
public static String matches(
|
||||
@Nullable String mimeType, @NonNull String[] filters) {
|
||||
if (mimeType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String[] mimeTypeParts = mimeType.split("/");
|
||||
for (String filter : filters) {
|
||||
final String[] filterParts = filter.split("/");
|
||||
if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches multiple MIME types against an array of MIME type filters.
|
||||
* @return The first matching MIME type, or null if nothing matches.
|
||||
*/
|
||||
@Nullable
|
||||
public static String matches(
|
||||
@Nullable String[] mimeTypes, @NonNull String filter) {
|
||||
if (mimeTypes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String[] filterParts = filter.split("/");
|
||||
for (String mimeType : mimeTypes) {
|
||||
final String[] mimeTypeParts = mimeType.split("/");
|
||||
if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
|
||||
return mimeType;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches multiple MIME types against an array of MIME type filters.
|
||||
* @return The list of matching MIME types, or empty array if nothing matches.
|
||||
*/
|
||||
@NonNull
|
||||
public static String[] matchesMany(
|
||||
@Nullable String[] mimeTypes, @NonNull String filter) {
|
||||
if (mimeTypes == null) {
|
||||
return new String[] {};
|
||||
}
|
||||
|
||||
final ArrayList<String> list = new ArrayList<>();
|
||||
final String[] filterParts = filter.split("/");
|
||||
for (String mimeType : mimeTypes) {
|
||||
final String[] mimeTypeParts = mimeType.split("/");
|
||||
if (mimeTypeAgainstFilter(mimeTypeParts, filterParts)) {
|
||||
list.add(mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,11 @@
|
||||
|
||||
package android.provider;
|
||||
|
||||
import static android.system.OsConstants.SEEK_SET;
|
||||
|
||||
import static com.android.internal.util.Preconditions.checkArgument;
|
||||
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
|
||||
import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UnsupportedAppUsage;
|
||||
import android.content.ContentProviderClient;
|
||||
@@ -29,13 +28,12 @@ import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.MimeTypeFilter;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
@@ -50,20 +48,13 @@ import android.os.Parcelable;
|
||||
import android.os.ParcelableException;
|
||||
import android.os.RemoteException;
|
||||
import android.os.storage.StorageVolume;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.util.DataUnit;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -112,6 +103,54 @@ public final class DocumentsContract {
|
||||
/** {@hide} */
|
||||
public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI";
|
||||
|
||||
/**
|
||||
* Key for {@link DocumentsProvider} to query display name is matched.
|
||||
* The match of display name is partial matching and case-insensitive.
|
||||
* Ex: The value is "o", the display name of the results will contain
|
||||
* both "foo" and "Open".
|
||||
*
|
||||
* @see DocumentsProvider#querySearchDocuments(String, String[],
|
||||
* Bundle)
|
||||
* {@hide}
|
||||
*/
|
||||
public static final String QUERY_ARG_DISPLAY_NAME = "android:query-arg-display-name";
|
||||
|
||||
/**
|
||||
* Key for {@link DocumentsProvider} to query mime types is matched.
|
||||
* The value is a string array, it can support different mime types.
|
||||
* Each items will be treated as "OR" condition. Ex: {"image/*" ,
|
||||
* "video/*"}. The mime types of the results will contain both image
|
||||
* type and video type.
|
||||
*
|
||||
* @see DocumentsProvider#querySearchDocuments(String, String[],
|
||||
* Bundle)
|
||||
* {@hide}
|
||||
*/
|
||||
public static final String QUERY_ARG_MIME_TYPES = "android:query-arg-mime-types";
|
||||
|
||||
/**
|
||||
* Key for {@link DocumentsProvider} to query the file size in bytes is
|
||||
* larger than the value.
|
||||
*
|
||||
* @see DocumentsProvider#querySearchDocuments(String, String[],
|
||||
* Bundle)
|
||||
* {@hide}
|
||||
*/
|
||||
public static final String QUERY_ARG_FILE_SIZE_OVER = "android:query-arg-file-size-over";
|
||||
|
||||
/**
|
||||
* Key for {@link DocumentsProvider} to query the last modified time
|
||||
* is newer than the value. The unit is in milliseconds since
|
||||
* January 1, 1970 00:00:00.0 UTC.
|
||||
*
|
||||
* @see DocumentsProvider#querySearchDocuments(String, String[],
|
||||
* Bundle)
|
||||
* @see Document#COLUMN_LAST_MODIFIED
|
||||
* {@hide}
|
||||
*/
|
||||
public static final String QUERY_ARG_LAST_MODIFIED_AFTER =
|
||||
"android:query-arg-last-modified-after";
|
||||
|
||||
/**
|
||||
* Sets the desired initial location visible to user when file chooser is shown.
|
||||
*
|
||||
@@ -928,6 +967,89 @@ public final class DocumentsContract {
|
||||
.appendQueryParameter(PARAM_QUERY, query).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the values match the query arguments.
|
||||
*
|
||||
* @param queryArgs the query arguments
|
||||
* @param displayName the display time to check against
|
||||
* @param mimeType the mime type to check against
|
||||
* @param lastModified the last modified time to check against
|
||||
* @param size the size to check against
|
||||
* @hide
|
||||
*/
|
||||
public static boolean matchSearchQueryArguments(Bundle queryArgs, String displayName,
|
||||
String mimeType, long lastModified, long size) {
|
||||
if (queryArgs == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final String argDisplayName = queryArgs.getString(QUERY_ARG_DISPLAY_NAME, "");
|
||||
if (!argDisplayName.isEmpty()) {
|
||||
// TODO (118795812) : Enhance the search string handled in DocumentsProvider
|
||||
if (!displayName.toLowerCase().contains(argDisplayName.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final long argFileSize = queryArgs.getLong(QUERY_ARG_FILE_SIZE_OVER, -1 /* defaultValue */);
|
||||
if (argFileSize != -1 && size < argFileSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final long argLastModified = queryArgs.getLong(QUERY_ARG_LAST_MODIFIED_AFTER,
|
||||
-1 /* defaultValue */);
|
||||
if (argLastModified != -1 && lastModified < argLastModified) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String[] argMimeTypes = queryArgs.getStringArray(QUERY_ARG_MIME_TYPES);
|
||||
if (argMimeTypes != null && argMimeTypes.length > 0) {
|
||||
mimeType = Intent.normalizeMimeType(mimeType);
|
||||
for (String type : argMimeTypes) {
|
||||
if (MimeTypeFilter.matches(mimeType, Intent.normalizeMimeType(type))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the handled query arguments from the query bundle. The handled arguments are
|
||||
* {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME},
|
||||
* {@link DocumentsContract#QUERY_ARG_MIME_TYPES},
|
||||
* {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER} and
|
||||
* {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}.
|
||||
*
|
||||
* @param queryArgs the query arguments to be parsed.
|
||||
* @return the handled query arguments
|
||||
* @hide
|
||||
*/
|
||||
public static String[] getHandledQueryArguments(Bundle queryArgs) {
|
||||
if (queryArgs == null) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
final ArrayList<String> args = new ArrayList<>();
|
||||
if (queryArgs.keySet().contains(QUERY_ARG_DISPLAY_NAME)) {
|
||||
args.add(QUERY_ARG_DISPLAY_NAME);
|
||||
}
|
||||
|
||||
if (queryArgs.keySet().contains(QUERY_ARG_FILE_SIZE_OVER)) {
|
||||
args.add(QUERY_ARG_FILE_SIZE_OVER);
|
||||
}
|
||||
|
||||
if (queryArgs.keySet().contains(QUERY_ARG_LAST_MODIFIED_AFTER)) {
|
||||
args.add(QUERY_ARG_LAST_MODIFIED_AFTER);
|
||||
}
|
||||
|
||||
if (queryArgs.keySet().contains(QUERY_ARG_MIME_TYPES)) {
|
||||
args.add(QUERY_ARG_MIME_TYPES);
|
||||
}
|
||||
return args.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the given URI represents a {@link Document} backed by a
|
||||
* {@link DocumentsProvider}.
|
||||
@@ -1052,6 +1174,15 @@ public final class DocumentsContract {
|
||||
return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the search query from a Bundle
|
||||
* {@link #QUERY_ARG_DISPLAY_NAME}.
|
||||
* {@hide}
|
||||
*/
|
||||
public static String getSearchDocumentsQuery(@NonNull Bundle bundle) {
|
||||
return bundle.getString(QUERY_ARG_DISPLAY_NAME, "" /* defaultValue */);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@UnsupportedAppUsage
|
||||
public static Uri setManageMode(Uri uri) {
|
||||
|
||||
@@ -32,7 +32,6 @@ import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
|
||||
import static android.provider.DocumentsContract.buildTreeDocumentUri;
|
||||
import static android.provider.DocumentsContract.getDocumentId;
|
||||
import static android.provider.DocumentsContract.getRootId;
|
||||
import static android.provider.DocumentsContract.getSearchDocumentsQuery;
|
||||
import static android.provider.DocumentsContract.getTreeDocumentId;
|
||||
import static android.provider.DocumentsContract.isTreeUri;
|
||||
|
||||
@@ -47,6 +46,7 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.MimeTypeFilter;
|
||||
import android.content.UriMatcher;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
@@ -650,6 +650,55 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
throw new UnsupportedOperationException("Search not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return documents that match the given query under the requested
|
||||
* root. The returned documents should be sorted by relevance in descending
|
||||
* order. How documents are matched against the query string is an
|
||||
* implementation detail left to each provider, but it's suggested that at
|
||||
* least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
|
||||
* case-insensitive fashion.
|
||||
* <p>
|
||||
* If your provider is cloud-based, and you have some data cached or pinned
|
||||
* locally, you may return the local data immediately, setting
|
||||
* {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
|
||||
* you are still fetching additional data. Then, when the network data is
|
||||
* available, you can send a change notification to trigger a requery and
|
||||
* return the complete contents.
|
||||
* <p>
|
||||
* To support change notifications, you must
|
||||
* {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
|
||||
* Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
|
||||
* String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
|
||||
* android.database.ContentObserver, boolean)} with that Uri to send change
|
||||
* notifications.
|
||||
*
|
||||
* @param rootId the root to search under.
|
||||
* @param projection list of {@link Document} columns to put into the
|
||||
* cursor. If {@code null} all supported columns should be
|
||||
* included.
|
||||
* @param queryArgs the query arguments.
|
||||
* {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME},
|
||||
* {@link DocumentsContract#QUERY_ARG_MIME_TYPES},
|
||||
* {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER},
|
||||
* {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}.
|
||||
* @return cursor containing search result. Include
|
||||
* {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor}
|
||||
* extras {@link Bundle} when any QUERY_ARG_* value was honored
|
||||
* during the preparation of the results.
|
||||
*
|
||||
* @see ContentResolver#EXTRA_HONORED_ARGS
|
||||
* @see DocumentsContract#EXTRA_LOADING
|
||||
* @see DocumentsContract#EXTRA_INFO
|
||||
* @see DocumentsContract#EXTRA_ERROR
|
||||
* {@hide}
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
|
||||
throws FileNotFoundException {
|
||||
return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
|
||||
projection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejects the root. Throws {@link IllegalStateException} if ejection failed.
|
||||
*
|
||||
@@ -795,7 +844,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
* {@link #queryDocument(String, String[])},
|
||||
* {@link #queryRecentDocuments(String, String[])},
|
||||
* {@link #queryRoots(String[])}, and
|
||||
* {@link #querySearchDocuments(String, String, String[])}.
|
||||
* {@link #querySearchDocuments(String, String[], Bundle)}.
|
||||
*/
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection,
|
||||
@@ -812,7 +861,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
* @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal)
|
||||
* @see #queryDocument(String, String[])
|
||||
* @see #queryChildDocuments(String, String[], String)
|
||||
* @see #querySearchDocuments(String, String, String[])
|
||||
* @see #querySearchDocuments(String, String[], Bundle)
|
||||
*/
|
||||
@Override
|
||||
public final Cursor query(
|
||||
@@ -825,8 +874,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
return queryRecentDocuments(
|
||||
getRootId(uri), projection, queryArgs, cancellationSignal);
|
||||
case MATCH_SEARCH:
|
||||
return querySearchDocuments(
|
||||
getRootId(uri), getSearchDocumentsQuery(uri), projection);
|
||||
return querySearchDocuments(getRootId(uri), projection, queryArgs);
|
||||
case MATCH_DOCUMENT:
|
||||
case MATCH_DOCUMENT_TREE:
|
||||
enforceTree(uri);
|
||||
@@ -1301,7 +1349,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
final long flags =
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
|
||||
if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
|
||||
mimeTypeMatches(mimeTypeFilter, mimeType)) {
|
||||
MimeTypeFilter.matches(mimeType, mimeTypeFilter)) {
|
||||
return new String[] { mimeType };
|
||||
}
|
||||
}
|
||||
@@ -1354,21 +1402,4 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
// For any other yet unhandled case, let the provider subclass handle it.
|
||||
return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static boolean mimeTypeMatches(String filter, String test) {
|
||||
if (test == null) {
|
||||
return false;
|
||||
} else if (filter == null || "*/*".equals(filter)) {
|
||||
return true;
|
||||
} else if (filter.equals(test)) {
|
||||
return true;
|
||||
} else if (filter.endsWith("/*")) {
|
||||
return filter.regionMatches(0, test, 0, filter.indexOf('/'));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,14 +389,18 @@ public abstract class FileSystemProvider extends DocumentsProvider {
|
||||
* @param query the search condition used to match file names
|
||||
* @param projection projection of the returned cursor
|
||||
* @param exclusion absolute file paths to exclude from result
|
||||
* @return cursor containing search result
|
||||
* @param queryArgs the query arguments for search
|
||||
* @return cursor containing search result. Include
|
||||
* {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor}
|
||||
* extras {@link Bundle} when any QUERY_ARG_* value was honored
|
||||
* during the preparation of the results.
|
||||
* @throws FileNotFoundException when root folder doesn't exist or search fails
|
||||
*
|
||||
* @see ContentResolver#EXTRA_HONORED_ARGS
|
||||
*/
|
||||
protected final Cursor querySearchDocuments(
|
||||
File folder, String query, String[] projection, Set<String> exclusion)
|
||||
File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
|
||||
throws FileNotFoundException {
|
||||
|
||||
query = query.toLowerCase();
|
||||
final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
|
||||
final LinkedList<File> pending = new LinkedList<>();
|
||||
pending.add(folder);
|
||||
@@ -407,11 +411,18 @@ public abstract class FileSystemProvider extends DocumentsProvider {
|
||||
pending.add(child);
|
||||
}
|
||||
}
|
||||
if (file.getName().toLowerCase().contains(query)
|
||||
&& !exclusion.contains(file.getAbsolutePath())) {
|
||||
if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file,
|
||||
queryArgs)) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
|
||||
final String[] handledQueryArgs = DocumentsContract.getHandledQueryArguments(queryArgs);
|
||||
if (handledQueryArgs.length > 0) {
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, handledQueryArgs);
|
||||
result.setExtras(extras);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -457,6 +468,34 @@ public abstract class FileSystemProvider extends DocumentsProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the file matches the query arguments.
|
||||
*
|
||||
* @param file the file to test
|
||||
* @param queryArgs the query arguments
|
||||
*/
|
||||
private boolean matchSearchQueryArguments(File file, Bundle queryArgs) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String fileMimeType;
|
||||
final String fileName = file.getName();
|
||||
|
||||
if (file.isDirectory()) {
|
||||
fileMimeType = DocumentsContract.Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
int dotPos = fileName.lastIndexOf('.');
|
||||
if (dotPos < 0) {
|
||||
return false;
|
||||
}
|
||||
final String extension = fileName.substring(dotPos + 1);
|
||||
fileMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
}
|
||||
return DocumentsContract.matchSearchQueryArguments(queryArgs, fileName, fileMimeType,
|
||||
file.lastModified(), file.length());
|
||||
}
|
||||
|
||||
private void scanFile(File visibleFile) {
|
||||
final Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||
intent.setData(Uri.fromFile(visibleFile));
|
||||
|
||||
@@ -541,14 +541,14 @@ public class ExternalStorageProvider extends FileSystemProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySearchDocuments(String rootId, String query, String[] projection)
|
||||
public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
|
||||
throws FileNotFoundException {
|
||||
final File parent;
|
||||
synchronized (mRootsLock) {
|
||||
parent = mRoots.get(rootId).path;
|
||||
}
|
||||
|
||||
return querySearchDocuments(parent, query, projection, Collections.emptySet());
|
||||
return querySearchDocuments(parent, projection, Collections.emptySet(), queryArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user