summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/list/CompositeCursorAdapter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher3/list/CompositeCursorAdapter.java')
-rw-r--r--src/com/android/launcher3/list/CompositeCursorAdapter.java532
1 files changed, 532 insertions, 0 deletions
diff --git a/src/com/android/launcher3/list/CompositeCursorAdapter.java b/src/com/android/launcher3/list/CompositeCursorAdapter.java
new file mode 100644
index 000000000..b163c501c
--- /dev/null
+++ b/src/com/android/launcher3/list/CompositeCursorAdapter.java
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2010 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 com.android.launcher3.list;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * A general purpose adapter that is composed of multiple cursors. It just
+ * appends them in the order they are added.
+ */
+public abstract class CompositeCursorAdapter extends BaseAdapter {
+
+ private static final int INITIAL_CAPACITY = 2;
+
+ public static class Partition {
+ boolean showIfEmpty;
+ boolean hasHeader;
+
+ Cursor cursor;
+ int idColumnIndex;
+ int count;
+
+ public Partition(boolean showIfEmpty, boolean hasHeader) {
+ this.showIfEmpty = showIfEmpty;
+ this.hasHeader = hasHeader;
+ }
+
+ /**
+ * True if the directory should be shown even if no contacts are found.
+ */
+ public boolean getShowIfEmpty() {
+ return showIfEmpty;
+ }
+
+ public boolean getHasHeader() {
+ return hasHeader;
+ }
+ }
+
+ private final Context mContext;
+ private ArrayList<Partition> mPartitions;
+ private int mCount = 0;
+ private boolean mCacheValid = true;
+ private boolean mNotificationsEnabled = true;
+ private boolean mNotificationNeeded;
+
+ public CompositeCursorAdapter(Context context) {
+ this(context, INITIAL_CAPACITY);
+ }
+
+ public CompositeCursorAdapter(Context context, int initialCapacity) {
+ mContext = context;
+ mPartitions = new ArrayList<Partition>();
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Registers a partition. The cursor for that partition can be set later.
+ * Partitions should be added in the order they are supposed to appear in the
+ * list.
+ */
+ public void addPartition(boolean showIfEmpty, boolean hasHeader) {
+ addPartition(new Partition(showIfEmpty, hasHeader));
+ }
+
+ public void addPartition(Partition partition) {
+ mPartitions.add(partition);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void addPartition(int location, Partition partition) {
+ mPartitions.add(location, partition);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void removePartition(int partitionIndex) {
+ Cursor cursor = mPartitions.get(partitionIndex).cursor;
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ mPartitions.remove(partitionIndex);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Removes cursors for all partitions.
+ */
+ // TODO: Is this really what this is supposed to do? Just remove the cursors? Not close them?
+ // Not remove the partitions themselves? Isn't this leaking?
+
+ public void clearPartitions() {
+ for (Partition partition : mPartitions) {
+ partition.cursor = null;
+ }
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Closes all cursors and removes all partitions.
+ */
+ public void close() {
+ for (Partition partition : mPartitions) {
+ Cursor cursor = partition.cursor;
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ }
+ mPartitions.clear();
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void setHasHeader(int partitionIndex, boolean flag) {
+ mPartitions.get(partitionIndex).hasHeader = flag;
+ invalidate();
+ }
+
+ public void setShowIfEmpty(int partitionIndex, boolean flag) {
+ mPartitions.get(partitionIndex).showIfEmpty = flag;
+ invalidate();
+ }
+
+ public Partition getPartition(int partitionIndex) {
+ return mPartitions.get(partitionIndex);
+ }
+
+ protected void invalidate() {
+ mCacheValid = false;
+ }
+
+ public int getPartitionCount() {
+ return mPartitions.size();
+ }
+
+ protected void ensureCacheValid() {
+ if (mCacheValid) {
+ return;
+ }
+
+ mCount = 0;
+ for (Partition partition : mPartitions) {
+ Cursor cursor = partition.cursor;
+ int count = cursor != null ? cursor.getCount() : 0;
+ if (partition.hasHeader) {
+ if (count != 0 || partition.showIfEmpty) {
+ count++;
+ }
+ }
+ partition.count = count;
+ mCount += count;
+ }
+
+ mCacheValid = true;
+ }
+
+ /**
+ * Returns true if the specified partition was configured to have a header.
+ */
+ public boolean hasHeader(int partition) {
+ return mPartitions.get(partition).hasHeader;
+ }
+
+ /**
+ * Returns the total number of list items in all partitions.
+ */
+ public int getCount() {
+ ensureCacheValid();
+ return mCount;
+ }
+
+ /**
+ * Returns the cursor for the given partition
+ */
+ public Cursor getCursor(int partition) {
+ return mPartitions.get(partition).cursor;
+ }
+
+ /**
+ * Changes the cursor for an individual partition.
+ */
+ public void changeCursor(int partition, Cursor cursor) {
+ Cursor prevCursor = mPartitions.get(partition).cursor;
+ if (prevCursor != cursor) {
+ if (prevCursor != null && !prevCursor.isClosed()) {
+ prevCursor.close();
+ }
+ mPartitions.get(partition).cursor = cursor;
+ if (cursor != null) {
+ mPartitions.get(partition).idColumnIndex = cursor.getColumnIndex("_id");
+ }
+ invalidate();
+ notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Returns true if the specified partition has no cursor or an empty cursor.
+ */
+ public boolean isPartitionEmpty(int partition) {
+ Cursor cursor = mPartitions.get(partition).cursor;
+ return cursor == null || cursor.getCount() == 0;
+ }
+
+ /**
+ * Given a list position, returns the index of the corresponding partition.
+ */
+ public int getPartitionForPosition(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ return i;
+ }
+ start = end;
+ }
+ return -1;
+ }
+
+ /**
+ * Given a list position, return the offset of the corresponding item in its
+ * partition. The header, if any, will have offset -1.
+ */
+ public int getOffsetInPartition(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (Partition partition : mPartitions) {
+ int end = start + partition.count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (partition.hasHeader) {
+ offset--;
+ }
+ return offset;
+ }
+ start = end;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the first list position for the specified partition.
+ */
+ public int getPositionForPartition(int partition) {
+ ensureCacheValid();
+ int position = 0;
+ for (int i = 0; i < partition; i++) {
+ position += mPartitions.get(i).count;
+ }
+ return position;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return getItemViewTypeCount() + 1;
+ }
+
+ /**
+ * Returns the overall number of item view types across all partitions. An
+ * implementation of this method needs to ensure that the returned count is
+ * consistent with the values returned by {@link #getItemViewType(int,int)}.
+ */
+ public int getItemViewTypeCount() {
+ return 1;
+ }
+
+ /**
+ * Returns the view type for the list item at the specified position in the
+ * specified partition.
+ */
+ protected int getItemViewType(int partition, int position) {
+ return 1;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions.get(i).hasHeader) {
+ offset--;
+ }
+ if (offset == -1) {
+ return IGNORE_ITEM_VIEW_TYPE;
+ } else {
+ return getItemViewType(i, offset);
+ }
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions.get(i).hasHeader) {
+ offset--;
+ }
+ View view;
+ if (offset == -1) {
+ view = getHeaderView(i, mPartitions.get(i).cursor, convertView, parent);
+ } else {
+ if (!mPartitions.get(i).cursor.moveToPosition(offset)) {
+ throw new IllegalStateException("Couldn't move cursor to position "
+ + offset);
+ }
+ view = getView(i, mPartitions.get(i).cursor, offset, convertView, parent);
+ }
+ if (view == null) {
+ throw new NullPointerException("View should not be null, partition: " + i
+ + " position: " + offset);
+ }
+ return view;
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ /**
+ * Returns the header view for the specified partition, creating one if needed.
+ */
+ protected View getHeaderView(int partition, Cursor cursor, View convertView,
+ ViewGroup parent) {
+ View view = convertView != null
+ ? convertView
+ : newHeaderView(mContext, partition, cursor, parent);
+ bindHeaderView(view, partition, cursor);
+ return view;
+ }
+
+ /**
+ * Creates the header view for the specified partition.
+ */
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ return null;
+ }
+
+ /**
+ * Binds the header view for the specified partition.
+ */
+ protected void bindHeaderView(View view, int partition, Cursor cursor) {
+ }
+
+ /**
+ * Returns an item view for the specified partition, creating one if needed.
+ */
+ protected View getView(int partition, Cursor cursor, int position, View convertView,
+ ViewGroup parent) {
+ View view;
+ if (convertView != null) {
+ view = convertView;
+ } else {
+ view = newView(mContext, partition, cursor, position, parent);
+ }
+ bindView(view, partition, cursor, position);
+ return view;
+ }
+
+ /**
+ * Creates an item view for the specified partition and position. Position
+ * corresponds directly to the current cursor position.
+ */
+ protected abstract View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent);
+
+ /**
+ * Binds an item view for the specified partition and position. Position
+ * corresponds directly to the current cursor position.
+ */
+ protected abstract void bindView(View v, int partition, Cursor cursor, int position);
+
+ /**
+ * Returns a pre-positioned cursor for the specified list position.
+ */
+ public Object getItem(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (Partition mPartition : mPartitions) {
+ int end = start + mPartition.count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartition.hasHeader) {
+ offset--;
+ }
+ if (offset == -1) {
+ return null;
+ }
+ Cursor cursor = mPartition.cursor;
+ cursor.moveToPosition(offset);
+ return cursor;
+ }
+ start = end;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the item ID for the specified list position.
+ */
+ public long getItemId(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (Partition mPartition : mPartitions) {
+ int end = start + mPartition.count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartition.hasHeader) {
+ offset--;
+ }
+ if (offset == -1) {
+ return 0;
+ }
+ if (mPartition.idColumnIndex == -1) {
+ return 0;
+ }
+
+ Cursor cursor = mPartition.cursor;
+ if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(offset)) {
+ return 0;
+ }
+ return cursor.getLong(mPartition.idColumnIndex);
+ }
+ start = end;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns false if any partition has a header.
+ */
+ @Override
+ public boolean areAllItemsEnabled() {
+ for (Partition mPartition : mPartitions) {
+ if (mPartition.hasHeader) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true for all items except headers.
+ */
+ @Override
+ public boolean isEnabled(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0, n = mPartitions.size(); i < n; i++) {
+ int end = start + mPartitions.get(i).count;
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions.get(i).hasHeader && offset == 0) {
+ return false;
+ } else {
+ return isEnabled(i, offset);
+ }
+ }
+ start = end;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the item at the specified offset of the specified
+ * partition is selectable and clickable.
+ */
+ protected boolean isEnabled(int partition, int position) {
+ return true;
+ }
+
+ /**
+ * Enable or disable data change notifications. It may be a good idea to
+ * disable notifications before making changes to several partitions at once.
+ */
+ public void setNotificationsEnabled(boolean flag) {
+ mNotificationsEnabled = flag;
+ if (flag && mNotificationNeeded) {
+ notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ if (mNotificationsEnabled) {
+ mNotificationNeeded = false;
+ super.notifyDataSetChanged();
+ } else {
+ mNotificationNeeded = true;
+ }
+ }
+} \ No newline at end of file