mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 14:45:29 +00:00
567 lines
15 KiB
Java
567 lines
15 KiB
Java
package org.mozilla.gecko.db;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
|
import org.mozilla.gecko.db.BrowserContract.TopSites;
|
|
|
|
import android.content.ContentResolver;
|
|
import android.database.CharArrayBuffer;
|
|
import android.database.ContentObserver;
|
|
import android.database.Cursor;
|
|
import android.database.DataSetObserver;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.util.SparseBooleanArray;
|
|
import android.util.SparseIntArray;
|
|
|
|
/**
|
|
* {@TopSitesCursorWrapper} is a cursor wrapper that merges
|
|
* the top and pinned sites cursors into one. It ensures the
|
|
* cursor will contain at least a given minimum number of
|
|
* entries.
|
|
*/
|
|
public class TopSitesCursorWrapper implements Cursor {
|
|
private enum RowType {
|
|
UNKNOWN,
|
|
BLANK,
|
|
TOP,
|
|
PINNED,
|
|
SUGGESTED
|
|
}
|
|
|
|
private static final String[] columnNames = new String[] {
|
|
TopSites._ID,
|
|
TopSites.URL,
|
|
TopSites.TITLE,
|
|
TopSites.BOOKMARK_ID,
|
|
TopSites.HISTORY_ID,
|
|
TopSites.TYPE,
|
|
};
|
|
|
|
private static final Map<String, Integer> columnIndexes =
|
|
new HashMap<String, Integer>(columnNames.length);
|
|
|
|
static {
|
|
for (int i = 0; i < columnNames.length; i++) {
|
|
columnIndexes.put(columnNames[i], i);
|
|
}
|
|
}
|
|
|
|
// Maps column indexes from the wrapper to the cursor's.
|
|
private SparseIntArray topIndexes;
|
|
private SparseIntArray pinnedIndexes;
|
|
private SparseIntArray suggestedIndexes;
|
|
|
|
// Type of content in the current position
|
|
private RowType currentRowType;
|
|
|
|
// Currently active cursor
|
|
private Cursor currentCursor;
|
|
|
|
// The cursor for the top sites query
|
|
private final Cursor topCursor;
|
|
|
|
// The cursor for the pinned sites query
|
|
private final Cursor pinnedCursor;
|
|
|
|
// The cursor for the sugested sites query
|
|
private final Cursor suggestedCursor;
|
|
|
|
// Associates pinned sites and their respective positions
|
|
private SparseBooleanArray pinnedPositions;
|
|
|
|
// Current position of the cursor
|
|
private int currentPosition = -1;
|
|
|
|
// Number of pinned sites before the current position
|
|
private int pinnedBefore;
|
|
|
|
// The size of the cursor wrapper
|
|
private int count;
|
|
|
|
// The minimum size of the cursor wrapper
|
|
private final int minSize;
|
|
|
|
public TopSitesCursorWrapper(Cursor pinnedCursor, Cursor topCursor, int minSize) {
|
|
this(pinnedCursor, topCursor, null, minSize);
|
|
}
|
|
|
|
public TopSitesCursorWrapper(Cursor pinnedCursor, Cursor topCursor, Cursor suggestedCursor, int minSize) {
|
|
currentRowType = RowType.UNKNOWN;
|
|
|
|
this.minSize = minSize;
|
|
this.topCursor = topCursor;
|
|
this.pinnedCursor = pinnedCursor;
|
|
this.suggestedCursor = suggestedCursor;
|
|
|
|
updateIndexMaps();
|
|
updatePinnedPositions();
|
|
updateCount();
|
|
}
|
|
|
|
private void updateIndexMaps() {
|
|
topIndexes = new SparseIntArray(topCursor.getColumnCount());
|
|
updateIndexMapFromCursor(topIndexes, topCursor);
|
|
|
|
pinnedIndexes = new SparseIntArray(pinnedCursor.getColumnCount());
|
|
updateIndexMapFromCursor(pinnedIndexes, pinnedCursor);
|
|
|
|
if (suggestedCursor != null) {
|
|
suggestedIndexes = new SparseIntArray(suggestedCursor.getColumnCount());
|
|
updateIndexMapFromCursor(suggestedIndexes, suggestedCursor);
|
|
}
|
|
}
|
|
|
|
private static void updateIndexMapFromCursor(SparseIntArray indexMap, Cursor c) {
|
|
final int columnCount = c.getColumnCount();
|
|
for (int i = 0; i < columnCount; i++) {
|
|
final Integer index = columnIndexes.get(c.getColumnName(i));
|
|
if (index != null) {
|
|
indexMap.put(index, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void updatePinnedPositions() {
|
|
if (pinnedPositions == null) {
|
|
pinnedPositions = new SparseBooleanArray();
|
|
} else {
|
|
pinnedPositions.clear();
|
|
}
|
|
|
|
pinnedCursor.moveToPosition(-1);
|
|
while (pinnedCursor.moveToNext()) {
|
|
int pos = pinnedCursor.getInt(pinnedCursor.getColumnIndex(Bookmarks.POSITION));
|
|
pinnedPositions.put(pos, true);
|
|
};
|
|
}
|
|
|
|
private void updateCount() {
|
|
int sum = pinnedCursor.getCount() + topCursor.getCount();
|
|
if (suggestedCursor != null) {
|
|
sum += suggestedCursor.getCount();
|
|
}
|
|
|
|
count = Math.max(minSize, sum);
|
|
}
|
|
|
|
private static boolean cursorHasValidPosition(Cursor c) {
|
|
return (c != null && !c.isBeforeFirst() && !c.isAfterLast());
|
|
}
|
|
|
|
private void updatePinnedBefore(int position) {
|
|
pinnedBefore = 0;
|
|
for (int i = 0; i < position; i++) {
|
|
if (pinnedPositions.get(i)) {
|
|
pinnedBefore++;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void setCurrentCursor(Cursor cursor, int position, RowType rowType) {
|
|
cursor.moveToPosition(position);
|
|
currentRowType = rowType;
|
|
currentCursor = cursor;
|
|
}
|
|
|
|
private boolean updateTopCursorPosition(int position) {
|
|
final int index = position - pinnedBefore;
|
|
if (index >= 0 && index < topCursor.getCount()) {
|
|
setCurrentCursor(topCursor, index, RowType.TOP);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private boolean updatePinnedCursorPosition(int position) {
|
|
if (position >= minSize) {
|
|
return false;
|
|
}
|
|
|
|
if (pinnedPositions.get(position)) {
|
|
setCurrentCursor(pinnedCursor, pinnedPositions.indexOfKey(position), RowType.PINNED);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private boolean updateSuggestedCursorPosition(int position) {
|
|
if (suggestedCursor == null) {
|
|
return false;
|
|
}
|
|
|
|
if (position >= minSize) {
|
|
return false;
|
|
}
|
|
|
|
final int index = position - pinnedBefore - topCursor.getCount();
|
|
if (index >= 0 && index < suggestedCursor.getCount()) {
|
|
setCurrentCursor(suggestedCursor, index, RowType.SUGGESTED);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void assertValidColumnIndex(int columnIndex) {
|
|
if (columnIndex < 0 || columnIndex > columnNames.length - 1) {
|
|
throw new IllegalArgumentException("Column index is out of bounds: " + columnIndex);
|
|
}
|
|
}
|
|
|
|
private void assertValidRowType() {
|
|
if (currentRowType == RowType.UNKNOWN) {
|
|
throw new IllegalStateException("No provided cursor holds data at this position");
|
|
}
|
|
}
|
|
|
|
private int getColumnIndexForCurrentRowType(int columnIndex) {
|
|
assertValidRowType();
|
|
assertValidColumnIndex(columnIndex);
|
|
|
|
SparseIntArray map = null;
|
|
|
|
switch (currentRowType) {
|
|
case TOP:
|
|
map = topIndexes;
|
|
break;
|
|
|
|
case PINNED:
|
|
map = pinnedIndexes;
|
|
break;
|
|
|
|
case SUGGESTED:
|
|
map = suggestedIndexes;
|
|
break;
|
|
}
|
|
|
|
if (map != null) {
|
|
return map.get(columnIndex, -1);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
@Override
|
|
public int getPosition() {
|
|
return currentPosition;
|
|
}
|
|
|
|
@Override
|
|
public int getCount() {
|
|
return count;
|
|
}
|
|
|
|
@Override
|
|
public boolean isAfterLast() {
|
|
return (currentPosition >= count);
|
|
}
|
|
|
|
@Override
|
|
public boolean isBeforeFirst() {
|
|
return (currentPosition < 0);
|
|
}
|
|
|
|
@Override
|
|
public boolean isFirst() {
|
|
return (currentPosition == 0);
|
|
}
|
|
|
|
@Override
|
|
public boolean isLast() {
|
|
return (currentPosition == count - 1);
|
|
}
|
|
|
|
@Override
|
|
public boolean moveToNext() {
|
|
return moveToPosition(currentPosition + 1);
|
|
}
|
|
|
|
@Override
|
|
public boolean moveToPrevious() {
|
|
return moveToPosition(currentPosition - 1);
|
|
}
|
|
|
|
@Override
|
|
public boolean moveToFirst() {
|
|
return moveToPosition(0);
|
|
}
|
|
|
|
@Override
|
|
public boolean moveToLast() {
|
|
return moveToPosition(count - 1);
|
|
}
|
|
|
|
@Override
|
|
public boolean move(int offset) {
|
|
return moveToPosition(currentPosition + offset);
|
|
}
|
|
|
|
@Override
|
|
public boolean moveToPosition(int position) {
|
|
currentPosition = position;
|
|
updatePinnedBefore(position);
|
|
|
|
if (!updatePinnedCursorPosition(position) &&
|
|
!updateTopCursorPosition(position) &&
|
|
!updateSuggestedCursorPosition(position)) {
|
|
|
|
if (position >= 0 && position < minSize) {
|
|
currentRowType = RowType.BLANK;
|
|
} else {
|
|
currentRowType = RowType.UNKNOWN;
|
|
}
|
|
currentCursor = null;
|
|
}
|
|
|
|
return cursorHasValidPosition(this);
|
|
}
|
|
|
|
@Override
|
|
public long getLong(int columnIndex) {
|
|
final int index = getColumnIndexForCurrentRowType(columnIndex);
|
|
if (index >= 0) {
|
|
return currentCursor.getLong(index);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public int getInt(int columnIndex) {
|
|
assertValidRowType();
|
|
assertValidColumnIndex(columnIndex);
|
|
|
|
if (columnNames[columnIndex].equals(TopSites.TYPE)) {
|
|
switch (currentRowType) {
|
|
case BLANK:
|
|
return TopSites.TYPE_BLANK;
|
|
|
|
case TOP:
|
|
return TopSites.TYPE_TOP;
|
|
|
|
case PINNED:
|
|
return TopSites.TYPE_PINNED;
|
|
|
|
case SUGGESTED:
|
|
return TopSites.TYPE_SUGGESTED;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
final int index = getColumnIndexForCurrentRowType(columnIndex);
|
|
if (index >= 0) {
|
|
return currentCursor.getInt(index);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public String getString(int columnIndex) {
|
|
final int index = getColumnIndexForCurrentRowType(columnIndex);
|
|
if (index >= 0) {
|
|
return currentCursor.getString(index);
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
@Override
|
|
public float getFloat(int columnIndex) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public double getDouble(int columnIndex) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public short getShort(int columnIndex) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public byte[] getBlob(int columnIndex) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public boolean isNull(int columnIndex) {
|
|
final int index = getColumnIndexForCurrentRowType(columnIndex);
|
|
if (index >= 0) {
|
|
return currentCursor.isNull(index);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int getType(int columnIndex) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public int getColumnCount() {
|
|
return columnNames.length;
|
|
}
|
|
|
|
@Override
|
|
public int getColumnIndex(String columnName) {
|
|
final Integer index = columnIndexes.get(columnName);
|
|
if (index == null) {
|
|
return -1;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
@Override
|
|
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
|
|
final int index = getColumnIndex(columnName);
|
|
if (index < 0) {
|
|
throw new IllegalArgumentException("Column index not found: " + columnName);
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
@Override
|
|
public String getColumnName(int columnIndex) {
|
|
return columnNames[columnIndex];
|
|
}
|
|
|
|
@Override
|
|
public String[] getColumnNames() {
|
|
return columnNames;
|
|
}
|
|
|
|
@Override
|
|
public boolean requery() {
|
|
boolean result = topCursor.requery() && pinnedCursor.requery();
|
|
if (suggestedCursor != null) {
|
|
result &= suggestedCursor.requery();
|
|
}
|
|
|
|
updatePinnedPositions();
|
|
updateCount();
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public Bundle respond(Bundle extras) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public Bundle getExtras() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public boolean getWantsAllOnMoveCalls() {
|
|
return false;
|
|
}
|
|
|
|
public Uri getNotificationUri() {
|
|
// There's no single notification URI for the wrapper
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void setNotificationUri(ContentResolver cr, Uri uri) {
|
|
// Keep the original notification URI for the
|
|
// wrapped cursors so that we get proper change
|
|
// notifications from the ContentResolver.
|
|
}
|
|
|
|
@Override
|
|
public void registerContentObserver(ContentObserver observer) {
|
|
topCursor.registerContentObserver(observer);
|
|
pinnedCursor.registerContentObserver(observer);
|
|
|
|
if (suggestedCursor != null) {
|
|
suggestedCursor.registerContentObserver(observer);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void unregisterContentObserver(ContentObserver observer) {
|
|
topCursor.unregisterContentObserver(observer);
|
|
pinnedCursor.unregisterContentObserver(observer);
|
|
|
|
if (suggestedCursor != null) {
|
|
suggestedCursor.unregisterContentObserver(observer);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void registerDataSetObserver(DataSetObserver observer) {
|
|
topCursor.registerDataSetObserver(observer);
|
|
pinnedCursor.registerDataSetObserver(observer);
|
|
|
|
if (suggestedCursor != null) {
|
|
suggestedCursor.registerDataSetObserver(observer);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void unregisterDataSetObserver(DataSetObserver observer) {
|
|
topCursor.unregisterDataSetObserver(observer);
|
|
pinnedCursor.unregisterDataSetObserver(observer);
|
|
|
|
if (suggestedCursor != null) {
|
|
suggestedCursor.unregisterDataSetObserver(observer);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void deactivate() {
|
|
topCursor.deactivate();
|
|
pinnedCursor.deactivate();
|
|
|
|
if (suggestedCursor != null) {
|
|
suggestedCursor.deactivate();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isClosed() {
|
|
boolean result = topCursor.isClosed() && pinnedCursor.isClosed();
|
|
|
|
if (suggestedCursor != null) {
|
|
result &= suggestedCursor.isClosed();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
topCursor.close();
|
|
topIndexes = null;
|
|
|
|
pinnedCursor.close();
|
|
pinnedIndexes = null;
|
|
pinnedPositions = null;
|
|
|
|
if (suggestedCursor != null) {
|
|
suggestedCursor.close();
|
|
suggestedIndexes = null;
|
|
}
|
|
}
|
|
}
|