content: /*
   * Copyright (C) 2006 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.BytesLong;
  import android.annotation.UnsupportedAppUsage;
  import android.content.res.Resources;
  import android.database.sqlite.SQLiteClosable;
  import android.database.sqlite.SQLiteException;
  import android.os.Binder;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.os.Process;
  import android.util.Log;
  import android.util.LongSparseArray;
  import android.util.SparseIntArray;
  
  import dalvik.annotation.optimization.FastNative;
  import dalvik.system.CloseGuard;
  
  /**
   * A buffer containing multiple cursor rows.
   * <p>
   * A {@link CursorWindow} is read-write when initially created and used locally.
   * When sent to a remote process (by writing it to a {@link Parcel}), the remote process
   * receives a read-only view of the cursor window.  Typically the cursor window
   * will be allocated by the producer, filled with data, and then sent to the
   * consumer for reading.
   * </p>
   */
  public class CursorWindow extends SQLiteClosable implements Parcelable {
      private static final String STATS_TAG = "CursorWindowStats";
  
      // This static member will be evaluated when first used.
      @UnsupportedAppUsage
      private static int sCursorWindowSize = -1;
  
      /**
       * The native CursorWindow object pointer.  (FOR INTERNAL USE ONLY)
       * @hide
       */
      @UnsupportedAppUsage
      public long mWindowPtr;
  
      private int mStartPos;
      private final String mName;
  
      private final CloseGuard mCloseGuard = CloseGuard.get();
  
      // May throw CursorWindowAllocationException
      private static native long nativeCreate(String name, int cursorWindowSize);
  
      // May throw CursorWindowAllocationException
      private static native long nativeCreateFromParcel(Parcel parcel);
      private static native void nativeDispose(long windowPtr);
      private static native void nativeWriteToParcel(long windowPtr, Parcel parcel);
  
      private static native String nativeGetName(long windowPtr);
      private static native byte[] nativeGetBlob(long windowPtr, int row, int column);
      private static native String nativeGetString(long windowPtr, int row, int column);
      private static native void nativeCopyStringToBuffer(long windowPtr, int row, int column,
              CharArrayBuffer buffer);
      private static native boolean nativePutBlob(long windowPtr, byte[] value, int row, int column);
      private static native boolean nativePutString(long windowPtr, String value,
              int row, int column);
  
      // Below native methods don't do unconstrained work, so are FastNative for performance
  
      @FastNative
      private static native void nativeClear(long windowPtr);
  
      @FastNative
      private static native int nativeGetNumRows(long windowPtr);
      @FastNative
      private static native boolean nativeSetNumColumns(long windowPtr, int columnNum);
      @FastNative
      private static native boolean nativeAllocRow(long windowPtr);
      @FastNative
      private static native void nativeFreeLastRow(long windowPtr);
  
      @FastNative
      private static native int nativeGetType(long windowPtr, int row, int column);
      @FastNative
      private static native long nativeGetLong(long windowPtr, int row, int column);
      @FastNative
      private static native double nativeGetDouble(long windowPtr, int row, int column);
  
      @FastNative
      private static native boolean nativePutLong(long windowPtr, long value, int row, int column);
      @FastNative
      private static native boolean nativePutDouble(long windowPtr, double value, int row, int column);
      @FastNative
      private static native boolean nativePutNull(long windowPtr, int row, int column);
  
  
      /**
       * Creates a new empty cursor window and gives it a name.
       * <p>
       * The cursor initially has no rows or columns.  Call {@link #setNumColumns(int)} to
       * set the number of columns before adding any rows to the cursor.
       * </p>
       *
       * @param name The name of the cursor window, or null if none.
       */
      public CursorWindow(String name) {
          this(name, getCursorWindowSize());
      }
  
      /**
       * Creates a new empty cursor window and gives it a name.
       * <p>
       * The cursor initially has no rows or columns.  Call {@link #setNumColumns(int)} to
       * set the number of columns before adding any rows to the cursor.
       * </p>
       *
       * @param name The name of the cursor window, or null if none.
       * @param windowSizeBytes Size of cursor window in bytes.
       * <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the
       * window. Depending on the amount of data stored, the actual amount of memory allocated can be
       * lower than specified size, but cannot exceed it.
       */
      public CursorWindow(String name, @BytesLong long windowSizeBytes) {
          mStartPos = 0;
          mName = name != null && name.length() != 0 ? name : "<unnamed>";
          mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
          if (mWindowPtr == 0) {
              throw new AssertionError(); // Not possible, the native code won't return it.
          }
          mCloseGuard.open("close");
          recordNewWindow(Binder.getCallingPid(), mWindowPtr);
      }
  
      /**
       * Creates a new empty cursor window.
       * <p>
       * The cursor initially has no rows or columns.  Call {@link #setNumColumns(int)} to
       * set the number of columns before adding any rows to the cursor.
       * </p>
       *
       * @param localWindow True if this window will be used in this process only,
       * false if it might be sent to another processes.  This argument is ignored.
       *
       * @deprecated There is no longer a distinction between local and remote
       * cursor windows.  Use the {@link #CursorWindow(String)} constructor instead.
       */
      @Deprecated
      public CursorWindow(boolean localWindow) {
          this((String)null);
      }
  
      private CursorWindow(Parcel source) {
          mStartPos = source.readInt();
          mWindowPtr = nativeCreateFromParcel(source);
          if (mWindowPtr == 0) {
              throw new AssertionError(); // Not possible, the native code won't return it.
          }
          mName = nativeGetName(mWindowPtr);
          mCloseGuard.open("close");
      }
  
      @Override
      protected void finalize() throws Throwable {
          try {
              if (mCloseGuard != null) {
                  mCloseGuard.warnIfOpen();
              }
              dispose();
          } finally {
              super.finalize();
          }
      }
  
      private void dispose() {
          if (mCloseGuard != null) {
              mCloseGuard.close();
          }
          if (mWindowPtr != 0) {
              recordClosingOfWindow(mWindowPtr);
              nativeDispose(mWindowPtr);
              mWindowPtr = 0;
          }
      }
  
      /**
       * Gets the name of this cursor window, never null.
       * @hide
       */
      public String getName() {
          return mName;
      }
  
      /**
       * Clears out the existing contents of the window, making it safe to reuse
       * for new data.
       * <p>
       * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}),
       * and number of columns in the cursor are all reset to zero.
       * </p>
       */
      public void clear() {
          acquireReference();
          try {
              mStartPos = 0;
              nativeClear(mWindowPtr);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Gets the start position of this cursor window.
       * <p>
       * The start position is the zero-based index of the first row that this window contains
       * relative to the entire result set of the {@link Cursor}.
       * </p>
       *
       * @return The zero-based start position.
       */
      public int getStartPosition() {
          return mStartPos;
      }
  
      /**
       * Sets the start position of this cursor window.
       * <p>
       * The start position is the zero-based index of the first row that this window contains
       * relative to the entire result set of the {@link Cursor}.
       * </p>
       *
       * @param pos The new zero-based start position.
       */
      public void setStartPosition(int pos) {
          mStartPos = pos;
      }
  
      /**
       * Gets the number of rows in this window.
       *
       * @return The number of rows in this cursor window.
       */
      public int getNumRows() {
          acquireReference();
          try {
              return nativeGetNumRows(mWindowPtr);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Sets the number of columns in this window.
       * <p>
       * This method must be called before any rows are added to the window, otherwise
       * it will fail to set the number of columns if it differs from the current number
       * of columns.
       * </p>
       *
       * @param columnNum The new number of columns.
       * @return True if successful.
       */
      public boolean setNumColumns(int columnNum) {
          acquireReference();
          try {
              return nativeSetNumColumns(mWindowPtr, columnNum);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Allocates a new row at the end of this cursor window.
       *
       * @return True if successful, false if the cursor window is out of memory.
       */
      public boolean allocRow(){
          acquireReference();
          try {
              return nativeAllocRow(mWindowPtr);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Frees the last row in this cursor window.
       */
      public void freeLastRow(){
          acquireReference();
          try {
              nativeFreeLastRow(mWindowPtr);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Returns true if the field at the specified row and column index
       * has type {@link Cursor#FIELD_TYPE_NULL}.
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}.
       * @deprecated Use {@link #getType(int, int)} instead.
       */
      @Deprecated
      public boolean isNull(int row, int column) {
          return getType(row, column) == Cursor.FIELD_TYPE_NULL;
      }
  
      /**
       * Returns true if the field at the specified row and column index
       * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}.
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or
       * {@link Cursor#FIELD_TYPE_NULL}.
       * @deprecated Use {@link #getType(int, int)} instead.
       */
      @Deprecated
      public boolean isBlob(int row, int column) {
          int type = getType(row, column);
          return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL;
      }
  
      /**
       * Returns true if the field at the specified row and column index
       * has type {@link Cursor#FIELD_TYPE_INTEGER}.
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}.
       * @deprecated Use {@link #getType(int, int)} instead.
       */
      @Deprecated
      public boolean isLong(int row, int column) {
          return getType(row, column) == Cursor.FIELD_TYPE_INTEGER;
      }
  
      /**
       * Returns true if the field at the specified row and column index
       * has type {@link Cursor#FIELD_TYPE_FLOAT}.
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}.
       * @deprecated Use {@link #getType(int, int)} instead.
       */
      @Deprecated
      public boolean isFloat(int row, int column) {
          return getType(row, column) == Cursor.FIELD_TYPE_FLOAT;
      }
  
      /**
       * Returns true if the field at the specified row and column index
       * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}.
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING}
       * or {@link Cursor#FIELD_TYPE_NULL}.
       * @deprecated Use {@link #getType(int, int)} instead.
       */
      @Deprecated
      public boolean isString(int row, int column) {
          int type = getType(row, column);
          return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL;
      }
  
      /**
       * Returns the type of the field at the specified row and column index.
       * <p>
       * The returned field types are:
       * <ul>
       * <li>{@link Cursor#FIELD_TYPE_NULL}</li>
       * <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
       * <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
       * <li>{@link Cursor#FIELD_TYPE_STRING}</li>
       * <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
       * </ul>
       * </p>
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return The field type.
       */
      public int getType(int row, int column) {
          acquireReference();
          try {
              return nativeGetType(mWindowPtr, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Gets the value of the field at the specified row and column index as a byte array.
       * <p>
       * The result is determined as follows:
       * <ul>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
       * is <code>null</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result
       * is the blob value.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
       * is the array of bytes that make up the internal representation of the
       * string value.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or
       * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.</li>
       * </ul>
       * </p>
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return The value of the field as a byte array.
       */
      public byte[] getBlob(int row, int column) {
          acquireReference();
          try {
              return nativeGetBlob(mWindowPtr, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Gets the value of the field at the specified row and column index as a string.
       * <p>
       * The result is determined as follows:
       * <ul>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
       * is <code>null</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
       * is the string value.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
       * is a string representation of the integer in decimal, obtained by formatting the
       * value with the <code>printf</code> family of functions using
       * format specifier <code>%lld</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
       * is a string representation of the floating-point value in decimal, obtained by
       * formatting the value with the <code>printf</code> family of functions using
       * format specifier <code>%g</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
       * {@link SQLiteException} is thrown.</li>
       * </ul>
       * </p>
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return The value of the field as a string.
       */
      public String getString(int row, int column) {
          acquireReference();
          try {
              return nativeGetString(mWindowPtr, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Copies the text of the field at the specified row and column index into
       * a {@link CharArrayBuffer}.
       * <p>
       * The buffer is populated as follows:
       * <ul>
       * <li>If the buffer is too small for the value to be copied, then it is
       * automatically resized.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer
       * is set to an empty string.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer
       * is set to the contents of the string.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer
       * is set to a string representation of the integer in decimal, obtained by formatting the
       * value with the <code>printf</code> family of functions using
       * format specifier <code>%lld</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is
       * set to a string representation of the floating-point value in decimal, obtained by
       * formatting the value with the <code>printf</code> family of functions using
       * format specifier <code>%g</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
       * {@link SQLiteException} is thrown.</li>
       * </ul>
       * </p>
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @param buffer The {@link CharArrayBuffer} to hold the string.  It is automatically
       * resized if the requested string is larger than the buffer's current capacity.
        */
      public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) {
          if (buffer == null) {
              throw new IllegalArgumentException("CharArrayBuffer should not be null");
          }
          acquireReference();
          try {
              nativeCopyStringToBuffer(mWindowPtr, row - mStartPos, column, buffer);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Gets the value of the field at the specified row and column index as a <code>long</code>.
       * <p>
       * The result is determined as follows:
       * <ul>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
       * is <code>0L</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
       * is the value obtained by parsing the string value with <code>strtoll</code>.
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
       * is the <code>long</code> value.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
       * is the floating-point value converted to a <code>long</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
       * {@link SQLiteException} is thrown.</li>
       * </ul>
       * </p>
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return The value of the field as a <code>long</code>.
       */
      public long getLong(int row, int column) {
          acquireReference();
          try {
              return nativeGetLong(mWindowPtr, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Gets the value of the field at the specified row and column index as a
       * <code>double</code>.
       * <p>
       * The result is determined as follows:
       * <ul>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
       * is <code>0.0</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
       * is the value obtained by parsing the string value with <code>strtod</code>.
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
       * is the integer value converted to a <code>double</code>.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
       * is the <code>double</code> value.</li>
       * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
       * {@link SQLiteException} is thrown.</li>
       * </ul>
       * </p>
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return The value of the field as a <code>double</code>.
       */
      public double getDouble(int row, int column) {
          acquireReference();
          try {
              return nativeGetDouble(mWindowPtr, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Gets the value of the field at the specified row and column index as a
       * <code>short</code>.
       * <p>
       * The result is determined by invoking {@link #getLong} and converting the
       * result to <code>short</code>.
       * </p>
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return The value of the field as a <code>short</code>.
       */
      public short getShort(int row, int column) {
          return (short) getLong(row, column);
      }
  
      /**
       * Gets the value of the field at the specified row and column index as an
       * <code>int</code>.
       * <p>
       * The result is determined by invoking {@link #getLong} and converting the
       * result to <code>int</code>.
       * </p>
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return The value of the field as an <code>int</code>.
       */
      public int getInt(int row, int column) {
          return (int) getLong(row, column);
      }
  
      /**
       * Gets the value of the field at the specified row and column index as a
       * <code>float</code>.
       * <p>
       * The result is determined by invoking {@link #getDouble} and converting the
       * result to <code>float</code>.
       * </p>
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return The value of the field as an <code>float</code>.
       */
      public float getFloat(int row, int column) {
          return (float) getDouble(row, column);
      }
  
      /**
       * Copies a byte array into the field at the specified row and column index.
       *
       * @param value The value to store.
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if successful.
       */
      public boolean putBlob(byte[] value, int row, int column) {
          acquireReference();
          try {
              return nativePutBlob(mWindowPtr, value, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Copies a string into the field at the specified row and column index.
       *
       * @param value The value to store.
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if successful.
       */
      public boolean putString(String value, int row, int column) {
          acquireReference();
          try {
              return nativePutString(mWindowPtr, value, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Puts a long integer into the field at the specified row and column index.
       *
       * @param value The value to store.
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if successful.
       */
      public boolean putLong(long value, int row, int column) {
          acquireReference();
          try {
              return nativePutLong(mWindowPtr, value, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Puts a double-precision floating point value into the field at the
       * specified row and column index.
       *
       * @param value The value to store.
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if successful.
       */
      public boolean putDouble(double value, int row, int column) {
          acquireReference();
          try {
              return nativePutDouble(mWindowPtr, value, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      /**
       * Puts a null value into the field at the specified row and column index.
       *
       * @param row The zero-based row index.
       * @param column The zero-based column index.
       * @return True if successful.
       */
      public boolean putNull(int row, int column) {
          acquireReference();
          try {
              return nativePutNull(mWindowPtr, row - mStartPos, column);
          } finally {
              releaseReference();
          }
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<CursorWindow> CREATOR
              = new Parcelable.Creator<CursorWindow>() {
          public CursorWindow createFromParcel(Parcel source) {
              return new CursorWindow(source);
          }
  
          public CursorWindow[] newArray(int size) {
              return new CursorWindow[size];
          }
      };
  
      public static CursorWindow newFromParcel(Parcel p) {
          return CREATOR.createFromParcel(p);
      }
  
      public int describeContents() {
          return 0;
      }
  
      public void writeToParcel(Parcel dest, int flags) {
          acquireReference();
          try {
              dest.writeInt(mStartPos);
              nativeWriteToParcel(mWindowPtr, dest);
          } finally {
              releaseReference();
          }
  
          if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
              releaseReference();
          }
      }
  
      @Override
      protected void onAllReferencesReleased() {
          dispose();
      }
  
      @UnsupportedAppUsage
      private static final LongSparseArray<Integer> sWindowToPidMap = new LongSparseArray<Integer>();
  
      private void recordNewWindow(int pid, long window) {
          synchronized (sWindowToPidMap) {
              sWindowToPidMap.put(window, pid);
              if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) {
                  Log.i(STATS_TAG, "Created a new Cursor. " + printStats());
              }
          }
      }
  
      private void recordClosingOfWindow(long window) {
          synchronized (sWindowToPidMap) {
              if (sWindowToPidMap.size() == 0) {
                  // this means we are not in the ContentProvider.
                  return;
              }
              sWindowToPidMap.delete(window);
          }
      }
  
      @UnsupportedAppUsage
      private String printStats() {
          StringBuilder buff = new StringBuilder();
          int myPid = Process.myPid();
          int total = 0;
          SparseIntArray pidCounts = new SparseIntArray();
          synchronized (sWindowToPidMap) {
              int size = sWindowToPidMap.size();
              if (size == 0) {
                  // this means we are not in the ContentProvider.
                  return "";
              }
              for (int indx = 0; indx < size; indx++) {
                  int pid = sWindowToPidMap.valueAt(indx);
                  int value = pidCounts.get(pid);
                  pidCounts.put(pid, ++value);
              }
          }
          int numPids = pidCounts.size();
          for (int i = 0; i < numPids;i++) {
              buff.append(" (# cursors opened by ");
              int pid = pidCounts.keyAt(i);
              if (pid == myPid) {
                  buff.append("this proc=");
              } else {
                  buff.append("pid " + pid + "=");
              }
              int num = pidCounts.get(pid);
              buff.append(num + ")");
              total += num;
          }
          // limit the returned string size to 1000
          String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString();
          return "# Open Cursors=" + total + s;
      }
  
      private static int getCursorWindowSize() {
          if (sCursorWindowSize < 0) {
              // The cursor window size. resource xml file specifies the value in kB.
              // convert it to bytes here by multiplying with 1024.
              sCursorWindowSize = Resources.getSystem().getInteger(
                      com.android.internal.R.integer.config_cursorWindowSize) * 1024;
          }
          return sCursorWindowSize;
      }
  
      @Override
      public String toString() {
          return getName() + " {" + Long.toHexString(mWindowPtr) + "}";
      }
  }
  