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

Commit 7d703471 authored by sabowitz's avatar sabowitz Committed by android-build-merger
Browse files

Merge changes I40cf6e7d,Iff515ae2,If8ca1f57,I1eec7a73,Iccb4e72c, ...

am: e479c7d4

Change-Id: Ib386b78d2b404eb60c2610daa343d77ff8758728
parents 33add58a e479c7d4
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -100,6 +100,8 @@ import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/** Adapter class to fill in data for the Call Log. */
public class CallLogAdapter extends GroupingListAdapter
@@ -158,9 +160,12 @@ public class CallLogAdapter extends GroupingListAdapter
  /**
   * Maps a raw input number to match info. We only log one MatchInfo per raw input number to reduce
   * the amount of data logged.
   *
   * <p>Note that this has to be a {@link ConcurrentMap} as the match info for each row in the UI is
   * loaded in a background thread spawned when the ViewHolder is bound.
   */
  private final Map<String, ContactsProviderMatchInfo> contactsProviderMatchInfos =
      new ArrayMap<>();
  private final ConcurrentMap<String, ContactsProviderMatchInfo> contactsProviderMatchInfos =
      new ConcurrentHashMap<>();

  private final ActionMode.Callback actionModeCallback =
      new ActionMode.Callback() {
@@ -1464,6 +1469,7 @@ public class CallLogAdapter extends GroupingListAdapter
    notifyDataSetChanged();
  }

  @WorkerThread
  private void logCp2Metrics(PhoneCallDetails details, ContactInfo contactInfo) {
    if (details == null) {
      return;
+26 −4
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.RemoteException;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.support.annotation.VisibleForTesting;
import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.NumberAttributes;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
@@ -33,6 +34,7 @@ import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
import com.android.dialer.inject.ApplicationContext;
import com.android.dialer.protos.ProtoParsers;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList;
@@ -49,13 +51,23 @@ public final class CallLogCacheUpdater {

  private final Context appContext;
  private final ListeningExecutorService backgroundExecutor;
  private final CallLogState callLogState;

  /**
   * Maximum numbers of operations the updater can do. Each transaction to the system call log will
   * trigger a call log refresh, so the updater can only do a single batch. If there are more
   * operations it will be truncated. Under normal circumstances there will only be 1 operation
   */
  @VisibleForTesting static final int CACHE_UPDATE_LIMIT = 100;

  @Inject
  CallLogCacheUpdater(
      @ApplicationContext Context appContext,
      @BackgroundExecutor ListeningExecutorService backgroundExecutor) {
      @BackgroundExecutor ListeningExecutorService backgroundExecutor,
      CallLogState callLogState) {
    this.appContext = appContext;
    this.backgroundExecutor = backgroundExecutor;
    this.callLogState = callLogState;
  }

  /**
@@ -66,17 +78,27 @@ public final class CallLogCacheUpdater {
   * has changed
   */
  public ListenableFuture<Void> updateCache(CallLogMutations mutations) {
    return backgroundExecutor.submit(
        () -> {
    return Futures.transform(
        callLogState.isBuilt(),
        isBuilt -> {
          if (!isBuilt) {
            // Initial build might need to update 1000 caches, which may overflow the batch
            // operation limit. The initial data was already built with the cache, there's no need
            // to update it.
            LogUtil.i("CallLogCacheUpdater.updateCache", "not updating cache for initial build");
            return null;
          }
          updateCacheInternal(mutations);
          return null;
        });
        },
        backgroundExecutor);
  }

  private void updateCacheInternal(CallLogMutations mutations) {
    ArrayList<ContentProviderOperation> operations = new ArrayList<>();
    Stream.concat(
            mutations.getInserts().entrySet().stream(), mutations.getUpdates().entrySet().stream())
        .limit(CACHE_UPDATE_LIMIT)
        .forEach(
            entry -> {
              ContentValues values = entry.getValue();
+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);
          }
        });
+26 −3
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ import android.text.method.LinkMovementMethod;
import android.view.ActionMode;
import android.view.DragEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
@@ -334,7 +335,12 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen
            activity.findViewById(R.id.remove_view),
            activity.findViewById(R.id.search_view_container),
            toolbar);
    speedDialFragmentHost = new MainSpeedDialFragmentHost(toolbar);
    speedDialFragmentHost =
        new MainSpeedDialFragmentHost(
            toolbar,
            activity.findViewById(R.id.root_layout),
            activity.findViewById(R.id.coordinator_layout),
            activity.findViewById(R.id.fragment_container));

    lastTabController = new LastTabController(activity, bottomNav, showVoicemailTab);

@@ -1255,15 +1261,32 @@ public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListen
  private static final class MainSpeedDialFragmentHost implements SpeedDialFragment.HostInterface {

    private final MainToolbar toolbar;

    MainSpeedDialFragmentHost(MainToolbar toolbar) {
    private final ViewGroup rootLayout;
    private final ViewGroup coordinatorLayout;
    private final ViewGroup fragmentContainer;

    MainSpeedDialFragmentHost(
        MainToolbar toolbar,
        ViewGroup rootLayout,
        ViewGroup coordinatorLayout,
        ViewGroup fragmentContainer) {
      this.toolbar = toolbar;
      this.rootLayout = rootLayout;
      this.coordinatorLayout = coordinatorLayout;
      this.fragmentContainer = fragmentContainer;
    }

    @Override
    public void setHasFrequents(boolean hasFrequents) {
      toolbar.showClearFrequents(hasFrequents);
    }

    @Override
    public void dragFavorite(boolean start) {
      rootLayout.setClipChildren(!start);
      coordinatorLayout.setClipChildren(!start);
      fragmentContainer.setClipChildren(!start);
    }
  }

  /**
Loading