From adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Thu, 23 Dec 2010 14:37:25 -0800 Subject: [PATCH] Log cursor/adapter class name and such when detecting a crash Also log where the cursor was closed, if it is. To investigate bug 3308465 and bug 3305706 Change-Id: I2b0fd9ea14757b6cf7597cd7162686d050d43fe9 --- src/com/android/email/Utility.java | 75 +++++++++++++++++++ .../activity/AccountSelectorAdapter.java | 18 ++++- .../email/activity/MailboxesAdapter.java | 23 +++++- .../email/activity/MessagesAdapter.java | 16 +++- .../com/android/email/UtilityUnitTests.java | 44 +++++++++++ 5 files changed, 170 insertions(+), 6 deletions(-) diff --git a/src/com/android/email/Utility.java b/src/com/android/email/Utility.java index 6f398a37c..620dece59 100644 --- a/src/com/android/email/Utility.java +++ b/src/com/android/email/Utility.java @@ -37,6 +37,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.Cursor; +import android.database.CursorWrapper; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; @@ -1180,4 +1181,78 @@ public class Utility { } return name; } + + /** + * Stringify a cursor for logging purpose. + */ + public static String dumpCursor(Cursor c) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + while (c != null) { + sb.append(c.getClass()); // Class name may not be available if toString() is overridden + sb.append("/"); + sb.append(c.toString()); + if (c.isClosed()) { + sb.append(" (closed)"); + } + if (c instanceof CursorWrapper) { + c = ((CursorWrapper) c).getWrappedCursor(); + sb.append(", "); + } else { + break; + } + } + sb.append("]"); + return sb.toString(); + } + + /** + * Cursor wrapper that remembers where it was closed. + * + * Use {@link #get} to create a wrapped cursor. + * USe {@link #getTraceIfAvailable} to get the stack trace. + * Use {@link #log} to log if/where it was closed. + */ + public static class CloseTraceCursorWrapper extends CursorWrapper { + private static final boolean TRACE_ENABLED = true; // STOPSHIP make it false + + private Exception mTrace; + + private CloseTraceCursorWrapper(Cursor cursor) { + super(cursor); + } + + @Override + public void close() { + mTrace = new Exception("STACK TRACE"); + super.close(); + } + + public static Exception getTraceIfAvailable(Cursor c) { + if (c instanceof CloseTraceCursorWrapper) { + return ((CloseTraceCursorWrapper) c).mTrace; + } else { + return null; + } + } + + public static void log(Cursor c) { + if (c == null) { + return; + } + if (c.isClosed()) { + Log.w(Email.LOG_TAG, "Cursor was closed here: Cursor=" + c, getTraceIfAvailable(c)); + } else { + Log.w(Email.LOG_TAG, "Cursor not closed. Cursor=" + c); + } + } + + public static Cursor get(Cursor original) { + return TRACE_ENABLED ? new CloseTraceCursorWrapper(original) : original; + } + + /* package */ static CloseTraceCursorWrapper alwaysCreateForTest(Cursor original) { + return new CloseTraceCursorWrapper(original); + } + } } diff --git a/src/com/android/email/activity/AccountSelectorAdapter.java b/src/com/android/email/activity/AccountSelectorAdapter.java index a2ebc0d17..6bd354598 100644 --- a/src/com/android/email/activity/AccountSelectorAdapter.java +++ b/src/com/android/email/activity/AccountSelectorAdapter.java @@ -16,7 +16,9 @@ package com.android.email.activity; +import com.android.email.Email; import com.android.email.R; +import com.android.email.Utility; import com.android.email.data.ThrottlingCursorLoader; import com.android.email.provider.EmailContent; import com.android.email.provider.EmailContent.Account; @@ -203,7 +205,7 @@ public class AccountSelectorAdapter extends CursorAdapter { countAccounts, countAccounts)); rb.add(totalUnread); } - return resultCursor; + return Utility.CloseTraceCursorWrapper.get(resultCursor); } } @@ -225,4 +227,18 @@ public class AccountSelectorAdapter extends CursorAdapter { super.close(); } } + + // STOPSHIP delete this + @Override + public long getItemId(int position) { + try { + return super.getItemId(position); + } catch (RuntimeException re) { + final Cursor c = getCursor(); + android.util.Log.w(Email.LOG_TAG, "Crash in getItemId, this=" + this + + " cursor=" + Utility.dumpCursor(c), re); + Utility.CloseTraceCursorWrapper.log(c); + throw re; + } + } } diff --git a/src/com/android/email/activity/MailboxesAdapter.java b/src/com/android/email/activity/MailboxesAdapter.java index a022c6c8c..19c4cb6f3 100644 --- a/src/com/android/email/activity/MailboxesAdapter.java +++ b/src/com/android/email/activity/MailboxesAdapter.java @@ -378,7 +378,7 @@ import android.widget.TextView; public Cursor loadInBackground() { final Cursor mailboxesCursor = super.loadInBackground(); if (mMode == MODE_MOVE_TO_TARGET) { - return mailboxesCursor; + return Utility.CloseTraceCursorWrapper.get(mailboxesCursor); } // Add "Starred". @@ -386,7 +386,7 @@ import android.widget.TextView; // starred. final int starredCount = Message.getFavoriteMessageCount(mContext); if (starredCount == 0) { - return mailboxesCursor; // no starred message + return Utility.CloseTraceCursorWrapper.get(mailboxesCursor); // no starred message } final MatrixCursor starredCursor = new MatrixCursor(getProjection()); @@ -394,7 +394,8 @@ import android.widget.TextView; addSummaryMailboxRow(mContext, starredCursor, Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL, starredCount, true); - return new MergeCursor(new Cursor[] {starredCursor, mailboxesCursor}); + return Utility.CloseTraceCursorWrapper.get( + new MergeCursor(new Cursor[] {starredCursor, mailboxesCursor})); } } @@ -440,7 +441,7 @@ import android.widget.TextView; accounts.close(); } - return combinedWithAccounts; + return Utility.CloseTraceCursorWrapper.get(combinedWithAccounts); } } @@ -501,4 +502,18 @@ import android.widget.TextView; /* package */ static int getUnreadCountForTest(Cursor cursor) { return cursor.getInt(COLUMN_UNREAD_COUNT); } + + // STOPSHIP delete this + @Override + public long getItemId(int position) { + try { + return super.getItemId(position); + } catch (RuntimeException re) { + final Cursor c = getCursor(); + android.util.Log.w(Email.LOG_TAG, "Crash in getItemId, this=" + this + + " cursor=" + Utility.dumpCursor(c), re); + Utility.CloseTraceCursorWrapper.log(c); + throw re; + } + } } \ No newline at end of file diff --git a/src/com/android/email/activity/MessagesAdapter.java b/src/com/android/email/activity/MessagesAdapter.java index c5a245cb4..2e307ab6c 100644 --- a/src/com/android/email/activity/MessagesAdapter.java +++ b/src/com/android/email/activity/MessagesAdapter.java @@ -227,7 +227,21 @@ import java.util.Set; setSelection(Utility.buildMailboxIdSelection(mContext, mMailboxId)); // Then do a query. - return super.loadInBackground(); + return Utility.CloseTraceCursorWrapper.get(super.loadInBackground()); + } + } + + // STOPSHIP delete this + @Override + public long getItemId(int position) { + try { + return super.getItemId(position); + } catch (RuntimeException re) { + final Cursor c = getCursor(); + android.util.Log.w(Email.LOG_TAG, "Crash in getItemId, this=" + this + + " cursor=" + Utility.dumpCursor(c), re); + Utility.CloseTraceCursorWrapper.log(c); + throw re; } } } diff --git a/tests/src/com/android/email/UtilityUnitTests.java b/tests/src/com/android/email/UtilityUnitTests.java index 85856fb59..25a3c2d60 100644 --- a/tests/src/com/android/email/UtilityUnitTests.java +++ b/tests/src/com/android/email/UtilityUnitTests.java @@ -24,6 +24,9 @@ import com.android.email.provider.EmailContent.Mailbox; import com.android.email.provider.ProviderTestUtils; import android.content.Context; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.database.MatrixCursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -34,6 +37,7 @@ import android.telephony.TelephonyManager; import android.test.AndroidTestCase; import android.test.MoreAsserts; import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; import android.util.Log; import android.widget.ListView; import android.widget.TextView; @@ -435,6 +439,46 @@ public class UtilityUnitTests extends AndroidTestCase { return ret; } + public void testDumpCursor() { + // Just make sure the method won't crash and returns non-empty string. + final Cursor c1 = new MatrixCursor(new String[] {"col"}); + final Cursor c2 = new CursorWrapper(c1); + + // Note it's a subclass of CursorWrapper. + final Cursor c3 = new CursorWrapper(c2) { + }; + + assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c1))); + assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c2))); + assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c3))); + assertFalse(TextUtils.isEmpty(Utility.dumpCursor(null))); + + // Test again with closed cursor. + c1.close(); + assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c1))); + assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c2))); + assertFalse(TextUtils.isEmpty(Utility.dumpCursor(c3))); + assertFalse(TextUtils.isEmpty(Utility.dumpCursor(null))); + } + + public void testCloseTraceCursorWrapper() { + final Cursor org = new MatrixCursor(new String[] {"col"}); + final Utility.CloseTraceCursorWrapper c = + Utility.CloseTraceCursorWrapper.alwaysCreateForTest(org); + + // Not closed -- no stack trace + assertNull(Utility.CloseTraceCursorWrapper.getTraceIfAvailable(c)); + Utility.CloseTraceCursorWrapper.log(c); // shouldn't crash + + // Close, now stack trace should be available + c.close(); + assertNotNull(Utility.CloseTraceCursorWrapper.getTraceIfAvailable(c)); + Utility.CloseTraceCursorWrapper.log(c); + + // shouldn't crash + Utility.CloseTraceCursorWrapper.log(null); + } + /** * A {@link ListView} used by {@link #testListStateSaver}. */