diff options
| author | 2018-11-16 12:59:43 -0800 | |
|---|---|---|
| committer | 2018-11-16 14:37:41 -0800 | |
| commit | d5db03df1f7cdf975d23c31dce283fc84fcbcebf (patch) | |
| tree | 78d3ab63b28ef8a6b1b612e6dc2db88214ecd571 | |
| parent | 992cd354fcebfd78ff7dc0eb2acf362d14497eef (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.java | 229 |
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); + } + } +} |