Don't use negative IDs with ListView

ListView uses the _id column for some operations, including
onSave/RestoreInstanceState, and if the column contains negative values
they don't work as expected.  The same assumption seems to be in other places
as well, so let's just avoid using negative IDs.

With this CL we now use two different IDs, one for ListView, which will
never be negative, and the other for us, the actual mailbox ID.

Bug 3049315

Change-Id: I263b4895212b5f8bb80c98acaf5c4eccd0bfef55
This commit is contained in:
Makoto Onuki 2010-10-12 17:02:07 -07:00
parent 9ce8f4d2a3
commit 9b5001a34c
3 changed files with 159 additions and 9 deletions

View File

@ -308,7 +308,8 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mCallback.onMailboxSelected(mAccountId, id);
// Don't use the id parameter. See MailboxesAdapter.
mCallback.onMailboxSelected(mAccountId, mListAdapter.getMailboxId(position));
}
public void onRefresh() {
@ -334,7 +335,7 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
}
final int count = mListView.getCount();
for (int i = 0; i < count; i++) {
if (mListView.getItemIdAtPosition(i) != mSelectedMailboxId) {
if (mListAdapter.getMailboxId(i) != mSelectedMailboxId) {
continue;
}
mListView.setItemChecked(i, true);

View File

@ -41,9 +41,14 @@ import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.security.InvalidParameterException;
/**
* The adapter for displaying mailboxes.
*
* Do not use {@link #getItemId(int)} -- It's only for ListView. Use {@link #getMailboxId}
* instead. (See the comment below)
*
* TODO New UI will probably not distinguish unread counts from # of messages.
* i.e. we won't need two different viewes for them.
* TODO Show "Starred" per account? (Right now we have only "All Starred")
@ -53,14 +58,25 @@ import android.widget.TextView;
public static final int MODE_NORMAL = 0;
public static final int MODE_MOVE_TO_TARGET = 1;
private static final String[] PROJECTION = new String[] { MailboxColumns.ID,
/*
* Note here we have two ID columns. The first one is for ListView, which doesn't like ID
* values to be negative. The second one is the actual mailbox ID, which we use in the rest
* of code.
* ListView uses row IDs for some operations, including onSave/RestoreInstanceState,
* and if we use negative IDs they don't work as expected.
* Because ListView finds the ID column by name ("_id"), we rename the second column
* so that ListView gets the correct column.
*/
/* package */ static final String[] PROJECTION = new String[] { MailboxColumns.ID,
MailboxColumns.ID + " AS org_mailbox_id",
MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT,
MailboxColumns.MESSAGE_COUNT};
private static final int COLUMN_ID = 0;
private static final int COLUMN_DISPLAY_NAME = 1;
private static final int COLUMN_TYPE = 2;
private static final int COLUMN_UNREAD_COUNT = 3;
private static final int COLUMN_MESSAGE_COUNT = 4;
// Column 0 is only for ListView; we don't use it in our code.
private static final int COLUMN_ID = 1;
private static final int COLUMN_DISPLAY_NAME = 2;
private static final int COLUMN_TYPE = 3;
private static final int COLUMN_UNREAD_COUNT = 4;
private static final int COLUMN_MESSAGE_COUNT = 5;
private static final String MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" +
" AND " + MailboxColumns.TYPE + "<" + Mailbox.TYPE_NOT_EMAIL +
@ -89,6 +105,11 @@ import android.widget.TextView;
mMode = mode;
}
public long getMailboxId(int position) {
Cursor c = (Cursor) getItem(position);
return c.getLong(COLUMN_ID);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
switch (mMode) {
@ -271,13 +292,33 @@ import android.widget.TextView;
private static void addSummaryMailboxRow(Context context, MatrixCursor cursor,
long id, int type, int count, boolean showAlways) {
if (id >= 0) {
throw new InvalidParameterException(); // Must be QUERY_ALL_*, which are all negative.
}
if (showAlways || (count > 0)) {
RowBuilder row = cursor.newRow();
row.add(id);
row.add(Long.MAX_VALUE + id); // Map QUERY_ALL_* constants to positive ints.
row.add(id); // The real mailbox ID.
row.add(""); // Display name. We get it from FolderProperties.
row.add(type);
row.add(count);
row.add(count);
}
}
/* package */ static long getIdForTest(Cursor cursor) {
return cursor.getLong(COLUMN_ID);
}
/* package */ static int getTypeForTest(Cursor cursor) {
return cursor.getInt(COLUMN_TYPE);
}
/* package */ static int getMessageCountForTest(Cursor cursor) {
return cursor.getInt(COLUMN_MESSAGE_COUNT);
}
/* package */ static int getUnreadCountForTest(Cursor cursor) {
return cursor.getInt(COLUMN_UNREAD_COUNT);
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.email.activity;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.Message;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import android.content.Context;
import android.database.Cursor;
import android.test.ProviderTestCase2;
import junit.framework.Assert;
public class MailboxesAdapterTest extends ProviderTestCase2<EmailProvider> {
private Context mMockContext;
public MailboxesAdapterTest() {
super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
}
@Override
public void setUp() throws Exception {
super.setUp();
mMockContext = getMockContext();
}
public void testAddSummaryMailboxRow() {
final Context c = mMockContext;
// Prepare test data
Account a1 = ProviderTestUtils.setupAccount("a1", true, c);
Account a2 = ProviderTestUtils.setupAccount("a2", true, c);
Mailbox b1i = ProviderTestUtils.setupMailbox("box1i", a1.mId, true, c, Mailbox.TYPE_INBOX);
Mailbox b2i = ProviderTestUtils.setupMailbox("box2i", a2.mId, true, c, Mailbox.TYPE_INBOX);
Mailbox b1o = ProviderTestUtils.setupMailbox("box1i", a1.mId, true, c, Mailbox.TYPE_OUTBOX);
Mailbox b2o = ProviderTestUtils.setupMailbox("box2i", a2.mId, true, c, Mailbox.TYPE_OUTBOX);
Mailbox b1d = ProviderTestUtils.setupMailbox("box1d", a1.mId, true, c, Mailbox.TYPE_DRAFTS);
Mailbox b2d = ProviderTestUtils.setupMailbox("box2d", a2.mId, true, c, Mailbox.TYPE_DRAFTS);
createMessage(c, b1i, false, false);
createMessage(c, b2i, true, true);
createMessage(c, b2i, true, false);
createMessage(c, b1o, true, true);
createMessage(c, b2o, false, true);
createMessage(c, b1d, false, true);
createMessage(c, b2d, false, true);
createMessage(c, b2d, false, true);
createMessage(c, b2d, false, true);
// Kick the method
Cursor cursor = MailboxesAdapter.getSpecialMailboxesCursor(c, true);
// Check the result
assertEquals(4, cursor.getCount());
// Row 1 -- combined inbox (with unread count)
assertTrue(cursor.moveToFirst());
checkSpecialMailboxRow(cursor, Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX, 2);
// Row 2 -- all starred (with total count)
assertTrue(cursor.moveToNext());
checkSpecialMailboxRow(cursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL, 3);
// Row 3 -- all drafts (with total count)
assertTrue(cursor.moveToNext());
checkSpecialMailboxRow(cursor, Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS, 4);
// Row 4 -- combined outbox (with total count)
assertTrue(cursor.moveToNext());
checkSpecialMailboxRow(cursor, Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX, 2);
}
private static Message createMessage(Context c, Mailbox b, boolean starred, boolean read) {
return ProviderTestUtils.setupMessage("m", b.mAccountKey, b.mId, false, true, c, starred,
read);
}
private static void checkSpecialMailboxRow(Cursor cursor, long id, int type,
int count) {
// _id must always be >= 0; otherwise ListView gets confused.
Assert.assertTrue(cursor.getLong(cursor.getColumnIndex("_id")) >= 0);
Assert.assertEquals(id, MailboxesAdapter.getIdForTest(cursor));
Assert.assertEquals(type, MailboxesAdapter.getTypeForTest(cursor));
Assert.assertEquals(count, MailboxesAdapter.getMessageCountForTest(cursor));
Assert.assertEquals(count, MailboxesAdapter.getUnreadCountForTest(cursor));
}
}