content: /*
   * 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.util.SparseArray;
  
  import java.util.Map;
  
  /**
   * Cursor that offers to redact values of requested columns.
   *
   * @hide
   */
  public class RedactingCursor extends CrossProcessCursorWrapper {
      private final SparseArray<Object> mRedactions;
  
      private RedactingCursor(@NonNull Cursor cursor, SparseArray<Object> redactions) {
          super(cursor);
          mRedactions = redactions;
      }
  
      /**
       * Create a wrapped instance of the given {@link Cursor} which redacts the
       * requested columns so they always return specific values when accessed.
       * <p>
       * If a redacted column appears multiple times in the underlying cursor, all
       * instances will be redacted. If none of the redacted columns appear in the
       * given cursor, the given cursor will be returned untouched to improve
       * performance.
       */
      public static Cursor create(@NonNull Cursor cursor, @NonNull Map<String, Object> redactions) {
          final SparseArray<Object> internalRedactions = new SparseArray<>();
  
          final String[] columns = cursor.getColumnNames();
          for (int i = 0; i < columns.length; i++) {
              if (redactions.containsKey(columns[i])) {
                  internalRedactions.put(i, redactions.get(columns[i]));
              }
          }
  
          if (internalRedactions.size() == 0) {
              return cursor;
          } else {
              return new RedactingCursor(cursor, internalRedactions);
          }
      }
  
      @Override
      public void fillWindow(int position, CursorWindow window) {
          // Fill window directly to ensure data is redacted
          DatabaseUtils.cursorFillWindow(this, position, window);
      }
  
      @Override
      public CursorWindow getWindow() {
          // Returning underlying window risks leaking redacted data
          return null;
      }
  
      @Override
      public Cursor getWrappedCursor() {
          throw new UnsupportedOperationException(
                  "Returning underlying cursor risks leaking redacted data");
      }
  
      @Override
      public double getDouble(int columnIndex) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              return (double) mRedactions.valueAt(i);
          } else {
              return super.getDouble(columnIndex);
          }
      }
  
      @Override
      public float getFloat(int columnIndex) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              return (float) mRedactions.valueAt(i);
          } else {
              return super.getFloat(columnIndex);
          }
      }
  
      @Override
      public int getInt(int columnIndex) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              return (int) mRedactions.valueAt(i);
          } else {
              return super.getInt(columnIndex);
          }
      }
  
      @Override
      public long getLong(int columnIndex) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              return (long) mRedactions.valueAt(i);
          } else {
              return super.getLong(columnIndex);
          }
      }
  
      @Override
      public short getShort(int columnIndex) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              return (short) mRedactions.valueAt(i);
          } else {
              return super.getShort(columnIndex);
          }
      }
  
      @Override
      public String getString(int columnIndex) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              return (String) mRedactions.valueAt(i);
          } else {
              return super.getString(columnIndex);
          }
      }
  
      @Override
      public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              buffer.data = ((String) mRedactions.valueAt(i)).toCharArray();
              buffer.sizeCopied = buffer.data.length;
          } else {
              super.copyStringToBuffer(columnIndex, buffer);
          }
      }
  
      @Override
      public byte[] getBlob(int columnIndex) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              return (byte[]) mRedactions.valueAt(i);
          } else {
              return super.getBlob(columnIndex);
          }
      }
  
      @Override
      public int getType(int columnIndex) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              return DatabaseUtils.getTypeOfObject(mRedactions.valueAt(i));
          } else {
              return super.getType(columnIndex);
          }
      }
  
      @Override
      public boolean isNull(int columnIndex) {
          final int i = mRedactions.indexOfKey(columnIndex);
          if (i >= 0) {
              return mRedactions.valueAt(i) == null;
          } else {
              return super.isNull(columnIndex);
          }
      }
  }
  