Merge "Check for Voice capability using the system resource"
diff --git a/Android.mk b/Android.mk
index e7624f7..1bebf33 100644
--- a/Android.mk
+++ b/Android.mk
@@ -5,7 +5,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := com.android.phone.common com.android.vcard
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.phone.common com.android.vcard android-common
LOCAL_PACKAGE_NAME := Contacts
LOCAL_CERTIFICATE := shared
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ecfe5c8..2458d80 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1088,6 +1088,8 @@
<!-- Generic action string for starting an IM chat -->
<string name="chat">Chat</string>
+ <!-- Field title for the full postal address of a contact [CHAR LIMIT=64]-->
+ <string name="postal_address">Address</string>
<!-- Field title for the street of a structured postal address of a contact -->
<string name="postal_street">Street</string>
<!-- Field title for the PO box of a structured postal address of a contact -->
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 708cdd8..1315922 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -16,13 +16,13 @@
package com.android.contacts.list;
+import com.android.common.widget.CompositeCursorAdapter.Partition;
import com.android.contacts.ContactEntryListView;
import com.android.contacts.ContactListEmptyView;
import com.android.contacts.ContactPhotoLoader;
import com.android.contacts.ContactsSearchManager;
import com.android.contacts.R;
import com.android.contacts.ui.ContactsPreferences;
-import com.android.contacts.widget.CompositeCursorAdapter.Partition;
import com.android.contacts.widget.ContextMenuAdapter;
import android.accounts.Account;
@@ -42,6 +42,7 @@
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Message;
import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.ContactsContract;
@@ -96,6 +97,9 @@
private static final int DIRECTORY_LOADER_ID = -1;
+ private static final int DIRECTORY_SEARCH_DELAY_MILLIS = 300;
+ private static final int DIRECTORY_SEARCH_MESSAGE = 1;
+
private boolean mSectionHeaderDisplayEnabled;
private boolean mPhotoLoaderEnabled;
private boolean mSearchMode;
@@ -140,6 +144,15 @@
private LoaderManager mLoaderManager;
+ private Handler mDelayedDirectorySearchHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == DIRECTORY_SEARCH_MESSAGE) {
+ loadDirectoryPartition(msg.arg1, (DirectoryPartition) msg.obj);
+ }
+ }
+ };
+
protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
protected abstract T createListAdapter();
@@ -315,16 +328,48 @@
private void startLoadingDirectoryPartition(int partitionIndex) {
DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
- Bundle args = new Bundle();
long directoryId = partition.getDirectoryId();
- args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
if (mForceLoad) {
- getLoaderManager().restartLoader(partitionIndex, args, this);
+ if (directoryId == Directory.DEFAULT) {
+ loadDirectoryPartition(partitionIndex, partition);
+ } else {
+ loadDirectoryPartitionDelayed(partitionIndex, partition);
+ }
} else {
+ Bundle args = new Bundle();
+ args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
getLoaderManager().initLoader(partitionIndex, args, this);
}
}
+ /**
+ * Queues up a delayed request to search the specified directory. Since
+ * directory search will likely introduce a lot of network traffic, we want
+ * to wait for a pause in the user's typing before sending a directory request.
+ */
+ private void loadDirectoryPartitionDelayed(int partitionIndex, DirectoryPartition partition) {
+ mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE, partition);
+ Message msg = mDelayedDirectorySearchHandler.obtainMessage(
+ DIRECTORY_SEARCH_MESSAGE, partitionIndex, 0, partition);
+ mDelayedDirectorySearchHandler.sendMessageDelayed(msg, DIRECTORY_SEARCH_DELAY_MILLIS);
+ }
+
+ /**
+ * Loads the directory partition.
+ */
+ protected void loadDirectoryPartition(int partitionIndex, DirectoryPartition partition) {
+ Bundle args = new Bundle();
+ args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
+ getLoaderManager().restartLoader(partitionIndex, args, this);
+ }
+
+ /**
+ * Cancels all queued directory loading requests.
+ */
+ private void removePendingDirectorySearchRequests() {
+ mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE);
+ }
+
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (!checkProviderStatus(false)) {
@@ -336,6 +381,7 @@
int loaderId = loader.getId();
if (loaderId == DIRECTORY_LOADER_ID) {
+ removePendingDirectorySearchRequests();
mAdapter.changeDirectories(data);
} else {
onPartitionLoaded(loaderId, data);
@@ -727,6 +773,7 @@
@Override
public void onPause() {
super.onPause();
+ removePendingDirectorySearchRequests();
unregisterProviderStatusObserver();
}
diff --git a/src/com/android/contacts/list/DirectoryPartition.java b/src/com/android/contacts/list/DirectoryPartition.java
index d7cb9bc..b55ed31 100644
--- a/src/com/android/contacts/list/DirectoryPartition.java
+++ b/src/com/android/contacts/list/DirectoryPartition.java
@@ -15,7 +15,7 @@
*/
package com.android.contacts.list;
-import com.android.contacts.widget.CompositeCursorAdapter;
+import com.android.common.widget.CompositeCursorAdapter;
import android.provider.ContactsContract.Directory;
diff --git a/src/com/android/contacts/model/ContactsSource.java b/src/com/android/contacts/model/ContactsSource.java
index d498398..b417224 100644
--- a/src/com/android/contacts/model/ContactsSource.java
+++ b/src/com/android/contacts/model/ContactsSource.java
@@ -25,11 +25,12 @@
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.view.View;
import android.widget.EditText;
import java.util.ArrayList;
@@ -221,10 +222,17 @@
public ContentValues defaultValues;
+ public Class<? extends View> editorClass;
+
public DataKind() {
}
public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable) {
+ this(mimeType, titleRes, iconRes, weight, editable, null);
+ }
+
+ public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable,
+ Class<? extends View> editorClass) {
this.mimeType = mimeType;
this.titleRes = titleRes;
this.iconRes = iconRes;
@@ -232,6 +240,7 @@
this.editable = editable;
this.isList = true;
this.typeOverallMax = -1;
+ this.editorClass = editorClass;
}
}
@@ -324,6 +333,11 @@
this.longForm = longForm;
return this;
}
+
+ public EditField setMinLines(int minLines) {
+ this.minLines = minLines;
+ return this;
+ }
}
/**
diff --git a/src/com/android/contacts/model/Editor.java b/src/com/android/contacts/model/Editor.java
index 04e023b..c73839d 100644
--- a/src/com/android/contacts/model/Editor.java
+++ b/src/com/android/contacts/model/Editor.java
@@ -58,6 +58,8 @@
public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly,
ViewIdGenerator vig);
+ public void setDeletable(boolean deletable);
+
/**
* Add a specific {@link EditorListener} to this {@link Editor}.
*/
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/EntityDelta.java
index 4f018a9..e353d70 100644
--- a/src/com/android/contacts/model/EntityDelta.java
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -578,6 +578,21 @@
}
}
+ public boolean isChanged(String key) {
+ if (mAfter == null || !mAfter.containsKey(key)) {
+ return false;
+ }
+
+ Object newValue = mAfter.get(key);
+ Object oldValue = mBefore.get(key);
+
+ if (oldValue == null) {
+ return newValue != null;
+ }
+
+ return !oldValue.equals(newValue);
+ }
+
public String getMimetype() {
return getAsString(Data.MIMETYPE);
}
diff --git a/src/com/android/contacts/model/FallbackSource.java b/src/com/android/contacts/model/FallbackSource.java
index a685e6d..e052fed 100644
--- a/src/com/android/contacts/model/FallbackSource.java
+++ b/src/com/android/contacts/model/FallbackSource.java
@@ -263,8 +263,6 @@
}
if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
- final boolean useJapaneseOrder =
- Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage());
kind.typeColumn = StructuredPostal.TYPE;
kind.typeList = Lists.newArrayList();
kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME));
@@ -274,38 +272,9 @@
.setCustomColumn(StructuredPostal.LABEL));
kind.fieldList = Lists.newArrayList();
-
- if (useJapaneseOrder) {
- kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
- R.string.postal_country, FLAGS_POSTAL).setOptional(true));
- kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
- R.string.postal_postcode, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.REGION,
- R.string.postal_region, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.CITY,
- R.string.postal_city, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
- R.string.postal_neighborhood, FLAGS_POSTAL).setOptional(true));
- kind.fieldList.add(new EditField(StructuredPostal.STREET,
- R.string.postal_street, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POBOX,
- R.string.postal_pobox, FLAGS_POSTAL).setOptional(true));
- } else {
- kind.fieldList.add(new EditField(StructuredPostal.STREET,
- R.string.postal_street, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POBOX,
- R.string.postal_pobox, FLAGS_POSTAL).setOptional(true));
- kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
- R.string.postal_neighborhood, FLAGS_POSTAL).setOptional(true));
- kind.fieldList.add(new EditField(StructuredPostal.CITY,
- R.string.postal_city, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.REGION,
- R.string.postal_region, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
- R.string.postal_postcode, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
- R.string.postal_country, FLAGS_POSTAL).setOptional(true));
- }
+ kind.fieldList.add(
+ new EditField(StructuredPostal.FORMATTED_ADDRESS, R.string.postal_address,
+ FLAGS_POSTAL).setMinLines(3));
}
return kind;
diff --git a/src/com/android/contacts/ui/widget/GenericEditorView.java b/src/com/android/contacts/ui/widget/GenericEditorView.java
index b5e0c4f..6be238f 100644
--- a/src/com/android/contacts/ui/widget/GenericEditorView.java
+++ b/src/com/android/contacts/ui/widget/GenericEditorView.java
@@ -43,6 +43,7 @@
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -164,13 +165,38 @@
// summarize the EditText heights
int totalHeight = 0;
+ int visibleFieldCount = 0;
+ EditText firstVisibleField = null;
if (mFieldEditTexts != null) {
for (EditText editText : mFieldEditTexts) {
if (editText.getVisibility() != View.GONE) {
+ visibleFieldCount ++;
+ if (firstVisibleField == null) {
+ firstVisibleField = editText;
+ }
totalHeight += editText.getMeasuredHeight();
}
}
}
+
+ int padding = getPaddingTop() + getPaddingBottom();
+ int minHeight = padding;
+
+ if (mMoreOrLess != null) {
+ minHeight += mMoreOrLess.getMeasuredHeight();
+ }
+
+ if (mDelete != null) {
+ minHeight += mDelete.getMeasuredHeight();
+ }
+
+ if (minHeight > totalHeight && visibleFieldCount == 1) {
+ firstVisibleField.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(minHeight - padding, MeasureSpec.EXACTLY));
+ }
+
+ totalHeight = Math.max(minHeight, totalHeight);
+
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec));
}
@@ -259,9 +285,7 @@
// Reconfigure GUI
mHideOptional = !mHideOptional;
- if (mListener != null) {
- mListener.onRequest(EditorListener.EDITOR_FORM_CHANGED);
- }
+ onOptionalFieldVisibilityChange();
rebuildValues();
// Restore focus
@@ -285,6 +309,12 @@
}
}
+ protected void onOptionalFieldVisibilityChange() {
+ if (mListener != null) {
+ mListener.onRequest(EditorListener.EDITOR_FORM_CHANGED);
+ }
+ }
+
public void setEditorListener(EditorListener listener) {
mListener = listener;
}
@@ -398,6 +428,7 @@
final EditText fieldView = new EditText(mContext);
fieldView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT));
+ fieldView.setGravity(Gravity.TOP);
mFieldEditTexts[index] = fieldView;
fieldView.setId(vig.getId(state, kind, entry, index));
if (field.titleRes > 0) {
diff --git a/src/com/android/contacts/ui/widget/KindSectionView.java b/src/com/android/contacts/ui/widget/KindSectionView.java
index cb20566..cd0b6fb 100644
--- a/src/com/android/contacts/ui/widget/KindSectionView.java
+++ b/src/com/android/contacts/ui/widget/KindSectionView.java
@@ -17,12 +17,12 @@
package com.android.contacts.ui.widget;
import com.android.contacts.R;
-import com.android.contacts.model.Editor;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityModifier;
import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.Editor;
import com.android.contacts.model.Editor.EditorListener;
+import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.EntityModifier;
import com.android.contacts.ui.ViewIdGenerator;
import android.content.Context;
@@ -137,13 +137,27 @@
if (!entry.isVisible()) continue;
if (isEmptyNoop(entry)) continue;
- final GenericEditorView editor = new GenericEditorView(mContext);
+ final View view;
+ if (mKind.editorClass == null) {
+ view = new GenericEditorView(mContext);
+ } else {
+ try {
+ view = mKind.editorClass.getConstructor(Context.class).newInstance(
+ mContext);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Cannot allocate editor for " + mKind.editorClass);
+ }
+ }
- editor.setPadding(0, 0, getThemeScrollbarSize(mContext), 0);
- editor.setValues(mKind, entry, mState, mReadOnly, mViewIdGenerator);
- editor.setEditorListener(this);
- editor.setDeletable(true);
- mEditors.addView(editor);
+ view.setPadding(0, 0, getThemeScrollbarSize(mContext), 0);
+ if (view instanceof Editor) {
+ Editor editor = (Editor) view;
+ editor.setValues(mKind, entry, mState, mReadOnly, mViewIdGenerator);
+ editor.setEditorListener(this);
+ editor.setDeletable(true);
+ }
+ mEditors.addView(view);
entryIndex++;
}
}
diff --git a/src/com/android/contacts/ui/widget/PhotoEditorView.java b/src/com/android/contacts/ui/widget/PhotoEditorView.java
index eff39d0..da1be85 100644
--- a/src/com/android/contacts/ui/widget/PhotoEditorView.java
+++ b/src/com/android/contacts/ui/widget/PhotoEditorView.java
@@ -168,4 +168,9 @@
public void setEditorListener(EditorListener listener) {
mListener = listener;
}
+
+ @Override
+ public void setDeletable(boolean deletable) {
+ // Photo is not deletable
+ }
}
diff --git a/src/com/android/contacts/widget/CompositeCursorAdapter.java b/src/com/android/contacts/widget/CompositeCursorAdapter.java
deleted file mode 100644
index c6aa775..0000000
--- a/src/com/android/contacts/widget/CompositeCursorAdapter.java
+++ /dev/null
@@ -1,492 +0,0 @@
-/*
- * 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.contacts.widget;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-/**
- * 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 Partition[] mPartitions;
- private int mSize = 0;
- private int mCount = 0;
- private boolean mCacheValid = true;
-
- public CompositeCursorAdapter(Context context) {
- this(context, INITIAL_CAPACITY);
- }
-
- public CompositeCursorAdapter(Context context, int initialCapacity) {
- mContext = context;
- mPartitions = new Partition[INITIAL_CAPACITY];
- }
-
- 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) {
- if (mSize >= mPartitions.length) {
- int newCapacity = mSize + 2;
- Partition[] newAdapters = new Partition[newCapacity];
- System.arraycopy(mPartitions, 0, newAdapters, 0, mSize);
- mPartitions = newAdapters;
- }
- mPartitions[mSize++] = partition;
- invalidate();
- notifyDataSetChanged();
- }
-
- public void removePartition(int partitionIndex) {
- Cursor cursor = mPartitions[partitionIndex].cursor;
- if (cursor != null && !cursor.isClosed()) {
- cursor.close();
- }
-
- System.arraycopy(mPartitions, partitionIndex + 1, mPartitions, partitionIndex,
- mSize - partitionIndex - 1);
- mSize--;
- invalidate();
- notifyDataSetChanged();
- }
-
- /**
- * Removes cursors for all partitions.
- */
- public void clearPartitions() {
- for (int i = 0; i < mSize; i++) {
- mPartitions[i].cursor = null;
- }
- invalidate();
- notifyDataSetChanged();
- }
-
- public void setHasHeader(int partitionIndex, boolean flag) {
- mPartitions[partitionIndex].hasHeader = flag;
- invalidate();
- }
-
- public void setShowIfEmpty(int partitionIndex, boolean flag) {
- mPartitions[partitionIndex].showIfEmpty = flag;
- invalidate();
- }
-
- public Partition getPartition(int partitionIndex) {
- if (partitionIndex >= mSize) {
- throw new ArrayIndexOutOfBoundsException(partitionIndex);
- }
- return mPartitions[partitionIndex];
- }
-
- protected void invalidate() {
- mCacheValid = false;
- }
-
- public int getPartitionCount() {
- return mSize;
- }
-
- protected void ensureCacheValid() {
- if (mCacheValid) {
- return;
- }
-
- mCount = 0;
- for (int i = 0; i < mSize; i++) {
- Cursor cursor = mPartitions[i].cursor;
- int count = cursor != null ? cursor.getCount() : 0;
- if (mPartitions[i].hasHeader) {
- if (count != 0 || mPartitions[i].showIfEmpty) {
- count++;
- }
- }
- mPartitions[i].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[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[partition].cursor;
- }
-
- /**
- * Changes the cursor for an individual partition.
- */
- public void changeCursor(int partition, Cursor cursor) {
- Cursor prevCursor = mPartitions[partition].cursor;
- if (prevCursor != cursor) {
- if (prevCursor != null && !prevCursor.isClosed()) {
- prevCursor.close();
- }
- mPartitions[partition].cursor = cursor;
- if (cursor != null) {
- mPartitions[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[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; i < mSize; i++) {
- int end = start + mPartitions[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 (int i = 0; i < mSize; i++) {
- int end = start + mPartitions[i].count;
- if (position >= start && position < end) {
- int offset = position - start;
- if (mPartitions[i].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[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; i < mSize; i++) {
- int end = start + mPartitions[i].count;
- if (position >= start && position < end) {
- int offset = position - start;
- if (mPartitions[i].hasHeader && offset == 0) {
- return IGNORE_ITEM_VIEW_TYPE;
- }
- return getItemViewType(i, position);
- }
- start = end;
- }
-
- throw new ArrayIndexOutOfBoundsException(position);
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- int end = start + mPartitions[i].count;
- if (position >= start && position < end) {
- int offset = position - start;
- if (mPartitions[i].hasHeader) {
- offset--;
- }
- View view;
- if (offset == -1) {
- view = getHeaderView(i, mPartitions[i].cursor, convertView, parent);
- } else {
- if (!mPartitions[i].cursor.moveToPosition(offset)) {
- throw new IllegalStateException("Couldn't move cursor to position "
- + offset);
- }
- view = getView(i, mPartitions[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 (int i = 0; i < mSize; i++) {
- int end = start + mPartitions[i].count;
- if (position >= start && position < end) {
- int offset = position - start;
- if (mPartitions[i].hasHeader) {
- offset--;
- }
- if (offset == -1) {
- return null;
- }
- Cursor cursor = mPartitions[i].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 (int i = 0; i < mSize; i++) {
- int end = start + mPartitions[i].count;
- if (position >= start && position < end) {
- int offset = position - start;
- if (mPartitions[i].hasHeader) {
- offset--;
- }
- if (offset == -1) {
- return 0;
- }
- if (mPartitions[i].idColumnIndex == -1) {
- return 0;
- }
-
- Cursor cursor = mPartitions[i].cursor;
- if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(offset)) {
- return 0;
- }
- return cursor.getLong(mPartitions[i].idColumnIndex);
- }
- start = end;
- }
-
- return 0;
- }
-
- /**
- * Returns false if any partition has a header.
- */
- @Override
- public boolean areAllItemsEnabled() {
- for (int i = 0; i < mSize; i++) {
- if (mPartitions[i].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; i < mSize; i++) {
- int end = start + mPartitions[i].count;
- if (position >= start && position < end) {
- int offset = position - start;
- if (mPartitions[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;
- }
-}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
index e39bce8..a4d375e 100644
--- a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
+++ b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
@@ -15,6 +15,8 @@
*/
package com.android.contacts.widget;
+import com.android.common.widget.CompositeCursorAdapter;
+
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
diff --git a/tests/src/com/android/contacts/widget/CompositeCursorAdapterTest.java b/tests/src/com/android/contacts/widget/CompositeCursorAdapterTest.java
deleted file mode 100644
index 813d2be..0000000
--- a/tests/src/com/android/contacts/widget/CompositeCursorAdapterTest.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * 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.contacts.widget;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Tests for {@link CompositeCursorAdapter}.
- */
-@SmallTest
-public class CompositeCursorAdapterTest extends AndroidTestCase {
-
- public class TestCompositeCursorAdapter extends CompositeCursorAdapter {
-
- public TestCompositeCursorAdapter() {
- super(CompositeCursorAdapterTest.this.getContext());
- }
-
- private StringBuilder mRequests = new StringBuilder();
-
- @Override
- protected View newHeaderView(Context context, int partition, Cursor cursor, ViewGroup parent) {
- return new View(context);
- }
-
- @Override
- protected void bindHeaderView(View view, int partition, Cursor cursor) {
- mRequests.append(partition + (cursor == null ? "" : cursor.getColumnNames()[0])
- + "[H] ");
- }
-
- @Override
- protected View newView(Context context, int sectionIndex, Cursor cursor, int position,
- ViewGroup parent) {
- return new View(context);
- }
-
- @Override
- protected void bindView(View v, int partition, Cursor cursor, int position) {
- if (!cursor.moveToPosition(position)) {
- fail("Invalid position:" + partition + " " + cursor.getColumnNames()[0] + " "
- + position);
- }
-
- mRequests.append(partition + cursor.getColumnNames()[0] + "["
- + cursor.getInt(0) + "] ");
- }
-
- @Override
- public String toString() {
- return mRequests.toString().trim();
- }
- }
-
- public void testGetCountNoEmptySections() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(false, false);
- adapter.addPartition(false, false);
-
- adapter.changeCursor(0, makeCursor("a", 2));
- adapter.changeCursor(1, makeCursor("b", 3));
-
- assertEquals(5, adapter.getCount());
- }
-
- public void testGetViewNoEmptySections() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(false, false);
- adapter.addPartition(false, false);
-
- adapter.changeCursor(0, makeCursor("a", 1));
- adapter.changeCursor(1, makeCursor("b", 2));
-
- for (int i = 0; i < adapter.getCount(); i++) {
- adapter.getView(i, null, null);
- }
-
- assertEquals("0a[0] 1b[0] 1b[1]", adapter.toString());
- }
-
- public void testGetCountWithHeadersAndNoEmptySections() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(false, true);
- adapter.addPartition(false, true);
-
- adapter.changeCursor(0, makeCursor("a", 2));
- adapter.changeCursor(1, makeCursor("b", 3));
-
- assertEquals(7, adapter.getCount());
- }
-
- public void testGetViewWithHeadersNoEmptySections() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(false, true);
- adapter.addPartition(false, true);
-
- adapter.changeCursor(0, makeCursor("a", 1));
- adapter.changeCursor(1, makeCursor("b", 2));
-
- for (int i = 0; i < adapter.getCount(); i++) {
- adapter.getView(i, null, null);
- }
-
- assertEquals("0a[H] 0a[0] 1b[H] 1b[0] 1b[1]", adapter.toString());
- }
-
- public void testGetCountWithHiddenEmptySection() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(false, true);
- adapter.addPartition(false, true);
-
- adapter.changeCursor(1, makeCursor("a", 2));
-
- assertEquals(3, adapter.getCount());
- }
-
- public void testGetPartitionForPosition() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(true, false);
- adapter.addPartition(true, true);
-
- adapter.changeCursor(0, makeCursor("a", 1));
- adapter.changeCursor(1, makeCursor("b", 2));
-
- assertEquals(0, adapter.getPartitionForPosition(0));
- assertEquals(1, adapter.getPartitionForPosition(1));
- assertEquals(1, adapter.getPartitionForPosition(2));
- assertEquals(1, adapter.getPartitionForPosition(3));
- }
-
- public void testGetOffsetForPosition() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(true, false);
- adapter.addPartition(true, true);
-
- adapter.changeCursor(0, makeCursor("a", 1));
- adapter.changeCursor(1, makeCursor("b", 2));
-
- assertEquals(0, adapter.getOffsetInPartition(0));
- assertEquals(-1, adapter.getOffsetInPartition(1));
- assertEquals(0, adapter.getOffsetInPartition(2));
- assertEquals(1, adapter.getOffsetInPartition(3));
- }
-
- public void testGetPositionForPartition() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(true, true);
- adapter.addPartition(true, true);
-
- adapter.changeCursor(0, makeCursor("a", 1));
- adapter.changeCursor(1, makeCursor("b", 2));
-
- assertEquals(0, adapter.getPositionForPartition(0));
- assertEquals(2, adapter.getPositionForPartition(1));
- }
-
- public void testGetViewWithHiddenEmptySections() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(false, false);
- adapter.addPartition(false, false);
-
- adapter.changeCursor(1, makeCursor("b", 2));
-
- for (int i = 0; i < adapter.getCount(); i++) {
- adapter.getView(i, null, null);
- }
-
- assertEquals("1b[0] 1b[1]", adapter.toString());
- }
-
- public void testGetCountWithShownEmptySection() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(true, true);
- adapter.addPartition(true, true);
-
- adapter.changeCursor(1, makeCursor("a", 2));
-
- assertEquals(4, adapter.getCount());
- }
-
- public void testGetViewWithShownEmptySections() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(true, true);
- adapter.addPartition(true, true);
-
- adapter.changeCursor(1, makeCursor("b", 2));
-
- for (int i = 0; i < adapter.getCount(); i++) {
- adapter.getView(i, null, null);
- }
-
- assertEquals("0[H] 1b[H] 1b[0] 1b[1]", adapter.toString());
- }
-
- public void testAreAllItemsEnabledFalse() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(true, false);
- adapter.addPartition(true, true);
-
- assertFalse(adapter.areAllItemsEnabled());
- }
-
- public void testAreAllItemsEnabledTrue() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(true, false);
- adapter.addPartition(true, false);
-
- assertTrue(adapter.areAllItemsEnabled());
- }
-
- public void testIsEnabled() {
- TestCompositeCursorAdapter adapter = new TestCompositeCursorAdapter();
- adapter.addPartition(true, false);
- adapter.addPartition(true, true);
-
- adapter.changeCursor(0, makeCursor("a", 1));
- adapter.changeCursor(1, makeCursor("b", 2));
-
- assertTrue(adapter.isEnabled(0));
- assertFalse(adapter.isEnabled(1));
- assertTrue(adapter.isEnabled(2));
- assertTrue(adapter.isEnabled(3));
- }
-
- private Cursor makeCursor(String name, int count) {
- MatrixCursor cursor = new MatrixCursor(new String[]{name});
- for (int i = 0; i < count; i++) {
- cursor.addRow(new Object[]{i});
- }
- return cursor;
- }
-}