summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Sudheer Shanka <sudheersai@google.com> 2018-11-16 12:59:43 -0800
committer Sudheer Shanka <sudheersai@google.com> 2018-11-16 14:37:41 -0800
commitd5db03df1f7cdf975d23c31dce283fc84fcbcebf (patch)
tree78d3ab63b28ef8a6b1b612e6dc2db88214ecd571
parent992cd354fcebfd78ff7dc0eb2acf362d14497eef (diff)
Move TranslatingCursor to frameworks/base.
so that it can be used by DownloadProvider as well. Also, override getColumnNames() as well since this is what is effectively used for getting the column count. Bug: 111890351 Test: atest MediaProviderTests Test: atest cts/tests/tests/provider/src/android/provider/cts/MediaStore* Change-Id: I336729c321614d923a31521379896ce1b88ad6b0
-rw-r--r--core/java/android/database/TranslatingCursor.java229
1 files changed, 229 insertions, 0 deletions
diff --git a/core/java/android/database/TranslatingCursor.java b/core/java/android/database/TranslatingCursor.java
new file mode 100644
index 000000000000..58e65b28f0e0
--- /dev/null
+++ b/core/java/android/database/TranslatingCursor.java
@@ -0,0 +1,229 @@
+/*
+ * 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.database;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.CancellationSignal;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Cursor that supports deprecation of {@code _data} like columns which represent raw filepaths,
+ * typically by replacing values with fake paths that the OS then offers to redirect to
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}, which developers
+ * should be using directly.
+ *
+ * @hide
+ */
+public class TranslatingCursor extends CrossProcessCursorWrapper {
+ public static class Config {
+ public final Uri baseUri;
+ public final String idColumn;
+ public final String[] filePathColumns;
+
+ public Config(Uri baseUri, String idColumn, String... filePathColumns) {
+ this.baseUri = baseUri;
+ this.idColumn = idColumn;
+ this.filePathColumns = filePathColumns;
+ }
+ }
+
+ public interface Translator {
+ String translate(String data, long id);
+ }
+
+ private final @NonNull Config mConfig;
+ private final @NonNull Translator mTranslator;
+ private final boolean mDropLast;
+
+ private final int mIdIndex;
+ private final int[] mFilePathColIndices;
+
+ private TranslatingCursor(@NonNull Cursor cursor, @NonNull Config config,
+ @NonNull Translator translator, boolean dropLast) {
+ super(cursor);
+
+ mConfig = Objects.requireNonNull(config);
+ mTranslator = Objects.requireNonNull(translator);
+ mDropLast = dropLast;
+
+ mIdIndex = cursor.getColumnIndexOrThrow(config.idColumn);
+ mFilePathColIndices = new int[config.filePathColumns.length];
+ for (int i = mFilePathColIndices.length - 1; i >= 0; --i) {
+ mFilePathColIndices[i] = cursor.getColumnIndex(config.filePathColumns[i]);
+ }
+ }
+
+ @Override
+ public int getColumnCount() {
+ if (mDropLast) {
+ return super.getColumnCount() - 1;
+ } else {
+ return super.getColumnCount();
+ }
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ if (mDropLast) {
+ return Arrays.copyOfRange(super.getColumnNames(), 0, super.getColumnCount() - 1);
+ } else {
+ return super.getColumnNames();
+ }
+ }
+
+ public static Cursor query(@NonNull Config config, @NonNull Translator translator,
+ SQLiteQueryBuilder qb, SQLiteDatabase db, String[] projectionIn, String selection,
+ String[] selectionArgs, String groupBy, String having, String sortOrder, String limit,
+ CancellationSignal signal) {
+ final boolean requestedId = ArrayUtils.isEmpty(projectionIn)
+ || ArrayUtils.contains(projectionIn, config.idColumn);
+ final boolean requestedData = ArrayUtils.isEmpty(projectionIn)
+ || ArrayUtils.containsAny(projectionIn, config.filePathColumns);
+
+ // If caller didn't request data, we have nothing to redirect
+ if (!requestedData || !ContentResolver.DEPRECATE_DATA_COLUMNS) {
+ return qb.query(db, projectionIn, selection, selectionArgs,
+ groupBy, having, sortOrder, limit, signal);
+ }
+
+ // If caller didn't request id, we need to splice it in
+ if (!requestedId) {
+ projectionIn = ArrayUtils.appendElement(String.class, projectionIn,
+ config.idColumn);
+ }
+
+ final Cursor c = qb.query(db, projectionIn, selection, selectionArgs,
+ groupBy, having, sortOrder);
+ return new TranslatingCursor(c, config, translator, !requestedId);
+ }
+
+ @Override
+ public void fillWindow(int position, CursorWindow window) {
+ // Fill window directly to ensure data is rewritten
+ DatabaseUtils.cursorFillWindow(this, position, window);
+ }
+
+ @Override
+ public CursorWindow getWindow() {
+ // Returning underlying window risks leaking data
+ return null;
+ }
+
+ @Override
+ public Cursor getWrappedCursor() {
+ throw new UnsupportedOperationException(
+ "Returning underlying cursor risks leaking data");
+ }
+
+ @Override
+ public double getDouble(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getDouble(columnIndex);
+ }
+ }
+
+ @Override
+ public float getFloat(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getFloat(columnIndex);
+ }
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getInt(columnIndex);
+ }
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getLong(columnIndex);
+ }
+ }
+
+ @Override
+ public short getShort(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getShort(columnIndex);
+ }
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ return mTranslator.translate(super.getString(columnIndex), super.getLong(mIdIndex));
+ } else {
+ return super.getString(columnIndex);
+ }
+ }
+
+ @Override
+ public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ super.copyStringToBuffer(columnIndex, buffer);
+ }
+ }
+
+ @Override
+ public byte[] getBlob(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ throw new IllegalArgumentException();
+ } else {
+ return super.getBlob(columnIndex);
+ }
+ }
+
+ @Override
+ public int getType(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ return Cursor.FIELD_TYPE_STRING;
+ } else {
+ return super.getType(columnIndex);
+ }
+ }
+
+ @Override
+ public boolean isNull(int columnIndex) {
+ if (ArrayUtils.contains(mFilePathColIndices, columnIndex)) {
+ return getString(columnIndex) == null;
+ } else {
+ return super.isNull(columnIndex);
+ }
+ }
+}