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
This commit is contained in:
Makoto Onuki 2010-12-23 14:37:25 -08:00
parent 2959a7e073
commit adbb6f8bc4
5 changed files with 170 additions and 6 deletions

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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}.
*/