Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6310dfd0 authored by linyuh's avatar linyuh Committed by Copybara-Service
Browse files

Also ignore IllegalStateException thrown by SQLiteClosable when coalescing fails.

Test: Manual
PiperOrigin-RevId: 202499434
Change-Id: Ie41eeb782072d82c5613b44be99649f43807498d
parent f375be6e
Loading
Loading
Loading
Loading
+65 −19
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.dialer.calllog.database;

import android.database.Cursor;
import android.database.StaleDataException;
import android.provider.CallLog.Calls;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
@@ -85,15 +86,16 @@ public class Coalescer {
  @WorkerThread
  @NonNull
  private ImmutableList<CoalescedRow> coalesceInternal(
      Cursor allAnnotatedCallLogRowsSortedByTimestampDesc) {
      Cursor allAnnotatedCallLogRowsSortedByTimestampDesc) throws ExpectedCoalescerException {
    Assert.isWorkerThread();

    ImmutableList.Builder<CoalescedRow> coalescedRowListBuilder = new ImmutableList.Builder<>();

    try {
      if (!allAnnotatedCallLogRowsSortedByTimestampDesc.moveToFirst()) {
        return ImmutableList.of();
      }

    ImmutableList.Builder<CoalescedRow> coalescedRowListBuilder = new ImmutableList.Builder<>();

      RowCombiner rowCombiner = new RowCombiner(allAnnotatedCallLogRowsSortedByTimestampDesc);
      rowCombiner.startNewGroup();

@@ -113,6 +115,43 @@ public class Coalescer {
      } while (!allAnnotatedCallLogRowsSortedByTimestampDesc.isAfterLast());

      return coalescedRowListBuilder.build();

    } catch (Exception exception) {
      // Coalescing can fail if cursor "allAnnotatedCallLogRowsSortedByTimestampDesc" is closed by
      // its loader while the work is still in progress.
      //
      // This can happen when the loader restarts and finishes loading data before the coalescing
      // work is completed.
      //
      // This kind of failure doesn't have to crash the app as coalescing will be restarted on the
      // latest data obtained by the loader. Therefore, we inspect the exception here and throw an
      // ExpectedCoalescerException if it is the case described above.
      //
      // The type of expected exception depends on whether AbstractWindowedCursor#checkPosition() is
      // called when the cursor is closed.
      //   (1) If it is called before the cursor is closed, we will get IllegalStateException thrown
      //       by SQLiteClosable when it attempts to acquire a reference to the database.
      //   (2) Otherwise, we will get StaleDataException thrown by AbstractWindowedCursor's
      //       checkPosition() method.
      //
      // Note that it would be more accurate to inspect the stack trace to locate the origin of the
      // exception. However, according to the documentation on Throwable#getStackTrace, "some
      // virtual machines may, under some circumstances, omit one or more stack frames from the
      // stack trace". "In the extreme case, a virtual machine that has no stack trace information
      // concerning this throwable is permitted to return a zero-length array from this method."
      // Therefore, the best we can do is to inspect the message in the exception.
      // TODO(linyuh): try to avoid the expected failure.
      String message = exception.getMessage();
      if (message != null
          && ((exception instanceof StaleDataException
                  && message.startsWith("Attempting to access a closed CursorWindow"))
              || (exception instanceof IllegalStateException
                  && message.startsWith("attempt to re-open an already-closed object")))) {
        throw new ExpectedCoalescerException(exception);
      }

      throw exception;
    }
  }

  /** Combines rows from {@link AnnotatedCallLog} into a {@link CoalescedRow}. */
@@ -337,4 +376,11 @@ public class Coalescer {
      return dialerPhoneNumberUtil.isMatch(groupPhoneNumber, rowPhoneNumber);
    }
  }

  /** A checked exception thrown when expected failure happens when coalescing is in progress. */
  public static final class ExpectedCoalescerException extends Exception {
    ExpectedCoalescerException(Throwable throwable) {
      super("Expected coalescing exception", throwable);
    }
  }
}
+8 −11
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@ package com.android.dialer.calllog.ui;

import android.app.Activity;
import android.database.Cursor;
import android.database.StaleDataException;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
@@ -34,6 +33,7 @@ import android.view.ViewGroup;
import com.android.dialer.calllog.CallLogComponent;
import com.android.dialer.calllog.RefreshAnnotatedCallLogReceiver;
import com.android.dialer.calllog.database.CallLogDatabaseComponent;
import com.android.dialer.calllog.database.Coalescer;
import com.android.dialer.calllog.model.CoalescedRow;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
@@ -337,16 +337,13 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback
          }
        },
        throwable -> {
          if (throwable instanceof StaleDataException) {
          // Coalescing can fail if the cursor passed to Coalescer is closed by the loader while
          // the work is still in progress.
          // This can happen when the loader restarts and finishes loading data before the
          // coalescing work is completed.
            // This failure doesn't need to be thrown as coalescing will be restarted on the latest
            // data obtained by the loader.
            // TODO(linyuh): Also throw an exception if the failure above can be avoided.
            LogUtil.e("NewCallLogFragment.onLoadFinished", "coalescing failed", throwable);
          } else {
          // This failure is identified by ExpectedCoalescerException and doesn't need to be
          // thrown as coalescing will be restarted on the latest data obtained by the loader.
          if (!(throwable instanceof Coalescer.ExpectedCoalescerException)) {
            throw new AssertionError(throwable);
          }
        });