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

Commit 1004ab95 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Update call log cache when annotated call log is updated."

parents 6b41e3a0 d8f2a8c6
Loading
Loading
Loading
Loading
+129 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.dialer.calllog;

import android.content.ContentProviderOperation;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.os.RemoteException;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.NumberAttributes;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.calllog.datasources.CallLogMutations;
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.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;
import javax.inject.Inject;

/**
 * Update {@link Calls#CACHED_NAME} and other cached columns after the annotated call log has been
 * updated. Dialer does not read these columns but other apps relies on it.
 */
@SuppressWarnings("AndroidApiChecker")
public final class CallLogCacheUpdater {

  private final Context appContext;
  private final ListeningExecutorService backgroundExecutor;

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

  /**
   * Extracts inserts and updates from {@code mutations} to update the 'cached' columns in the
   * system call log.
   *
   * <p>If the cached columns are non-empty, it will only be updated if {@link Calls#CACHED_NAME}
   * has changed
   */
  public ListenableFuture<Void> updateCache(CallLogMutations mutations) {
    return backgroundExecutor.submit(
        () -> {
          updateCacheInternal(mutations);
          return null;
        });
  }

  private void updateCacheInternal(CallLogMutations mutations) {
    ArrayList<ContentProviderOperation> operations = new ArrayList<>();
    Stream.concat(
            mutations.getInserts().entrySet().stream(), mutations.getUpdates().entrySet().stream())
        .forEach(
            entry -> {
              ContentValues values = entry.getValue();
              if (!values.containsKey(AnnotatedCallLog.NUMBER_ATTRIBUTES)
                  || !values.containsKey(AnnotatedCallLog.NUMBER)) {
                return;
              }
              DialerPhoneNumber dialerPhoneNumber =
                  ProtoParsers.getTrusted(
                      values, AnnotatedCallLog.NUMBER, DialerPhoneNumber.getDefaultInstance());
              NumberAttributes numberAttributes =
                  ProtoParsers.getTrusted(
                      values,
                      AnnotatedCallLog.NUMBER_ATTRIBUTES,
                      NumberAttributes.getDefaultInstance());
              operations.add(
                  ContentProviderOperation.newUpdate(
                          ContentUris.withAppendedId(Calls.CONTENT_URI, entry.getKey()))
                      .withValue(
                          Calls.CACHED_FORMATTED_NUMBER,
                          values.getAsString(AnnotatedCallLog.FORMATTED_NUMBER))
                      .withValue(Calls.CACHED_LOOKUP_URI, numberAttributes.getLookupUri())
                      // Calls.CACHED_MATCHED_NUMBER is not available.
                      .withValue(Calls.CACHED_NAME, numberAttributes.getName())
                      .withValue(
                          Calls.CACHED_NORMALIZED_NUMBER, dialerPhoneNumber.getNormalizedNumber())
                      .withValue(Calls.CACHED_NUMBER_LABEL, numberAttributes.getNumberTypeLabel())
                      // NUMBER_TYPE is lost in NumberAttributes when it is converted to a string
                      // label, Use TYPE_CUSTOM so the label will be displayed.
                      .withValue(Calls.CACHED_NUMBER_TYPE, Phone.TYPE_CUSTOM)
                      .withValue(Calls.CACHED_PHOTO_ID, numberAttributes.getPhotoId())
                      .withValue(Calls.CACHED_PHOTO_URI, numberAttributes.getPhotoUri())
                      // Avoid writing to the call log for insignificant changes to avoid triggering
                      // other content observers such as the voicemail client.
                      .withSelection(
                          Calls.CACHED_NAME + " IS NOT ?",
                          new String[] {numberAttributes.getName()})
                      .build());
            });
    try {
      int count =
          Arrays.stream(appContext.getContentResolver().applyBatch(CallLog.AUTHORITY, operations))
              .mapToInt(result -> result.count)
              .sum();
      LogUtil.i("CallLogCacheUpdater.updateCache", "updated %d rows", count);
    } catch (OperationApplicationException | RemoteException e) {
      throw new IllegalStateException(e);
    }
  }
}
+12 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.dialer.calllog.datasources.DataSources;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
import com.android.dialer.common.concurrent.Annotations.LightweightExecutor;
import com.android.dialer.common.concurrent.DefaultFutureCallback;
import com.android.dialer.common.concurrent.DialerFutureSerializer;
import com.android.dialer.common.concurrent.DialerFutures;
import com.android.dialer.inject.ApplicationContext;
@@ -53,6 +54,7 @@ public class RefreshAnnotatedCallLogWorker {
  private final MutationApplier mutationApplier;
  private final FutureTimer futureTimer;
  private final CallLogState callLogState;
  private final CallLogCacheUpdater callLogCacheUpdater;
  private final ListeningExecutorService backgroundExecutorService;
  private final ListeningExecutorService lightweightExecutorService;
  // Used to ensure that only one refresh flow runs at a time. (Note that
@@ -67,6 +69,7 @@ public class RefreshAnnotatedCallLogWorker {
      MutationApplier mutationApplier,
      FutureTimer futureTimer,
      CallLogState callLogState,
      CallLogCacheUpdater callLogCacheUpdater,
      @BackgroundExecutor ListeningExecutorService backgroundExecutorService,
      @LightweightExecutor ListeningExecutorService lightweightExecutorService) {
    this.appContext = appContext;
@@ -75,6 +78,7 @@ public class RefreshAnnotatedCallLogWorker {
    this.mutationApplier = mutationApplier;
    this.futureTimer = futureTimer;
    this.callLogState = callLogState;
    this.callLogCacheUpdater = callLogCacheUpdater;
    this.backgroundExecutorService = backgroundExecutorService;
    this.lightweightExecutorService = lightweightExecutorService;
  }
@@ -206,6 +210,14 @@ public class RefreshAnnotatedCallLogWorker {
            },
            lightweightExecutorService);

    Futures.addCallback(
        Futures.transformAsync(
            applyMutationsFuture,
            unused -> callLogCacheUpdater.updateCache(mutations),
            MoreExecutors.directExecutor()),
        new DefaultFutureCallback<>(),
        MoreExecutors.directExecutor());

    // After mutations applied, call onSuccessfulFill for each data source (in parallel).
    ListenableFuture<List<Void>> onSuccessfulFillFuture =
        Futures.transformAsync(
+34 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.dialer.protos;

import android.content.ContentValues;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
@@ -42,10 +43,27 @@ public final class ProtoParsers {
    return (T) mergeFrom(bytes, defaultInstance.getDefaultInstanceForType());
  }

  /**
   * Retrieve a proto from a ContentValues which was not created within the current
   * executable/version.
   */
  @SuppressWarnings("unchecked") // We want to eventually optimize away parser classes, so cast
  public static <T extends MessageLite> T get(
      @NonNull ContentValues contentValues, @NonNull String key, @NonNull T defaultInstance)
      throws InvalidProtocolBufferException {

    Assert.isNotNull(contentValues);
    Assert.isNotNull(key);
    Assert.isNotNull(defaultInstance);

    byte[] bytes = contentValues.getAsByteArray(key);
    return (T) mergeFrom(bytes, defaultInstance.getDefaultInstanceForType());
  }

  /**
   * Retrieve a proto from a trusted bundle which was created within the current executable/version.
   *
   * @throws RuntimeException if the proto cannot be parsed
   * @throws IllegalStateException if the proto cannot be parsed
   */
  public static <T extends MessageLite> T getTrusted(
      @NonNull Bundle bundle, @NonNull String key, @NonNull T defaultInstance) {
@@ -56,6 +74,21 @@ public final class ProtoParsers {
    }
  }

  /**
   * Retrieve a proto from a trusted ContentValues which was created within the current
   * executable/version.
   *
   * @throws IllegalStateException if the proto cannot be parsed
   */
  public static <T extends MessageLite> T getTrusted(
      @NonNull ContentValues contentValues, @NonNull String key, @NonNull T defaultInstance) {
    try {
      return get(contentValues, key, defaultInstance);
    } catch (InvalidProtocolBufferException e) {
      throw Assert.createIllegalStateFailException(e.toString());
    }
  }

  /**
   * Retrieve a proto from a trusted bundle which was created within the current executable/version.
   *