summaryrefslogtreecommitdiff
path: root/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'java/com')
-rw-r--r--java/com/android/dialer/calllog/CallLogCacheUpdater.java129
-rw-r--r--java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java12
-rw-r--r--java/com/android/dialer/protos/ProtoParsers.java35
3 files changed, 175 insertions, 1 deletions
diff --git a/java/com/android/dialer/calllog/CallLogCacheUpdater.java b/java/com/android/dialer/calllog/CallLogCacheUpdater.java
new file mode 100644
index 000000000..a7b2b3d0d
--- /dev/null
+++ b/java/com/android/dialer/calllog/CallLogCacheUpdater.java
@@ -0,0 +1,129 @@
+/*
+ * 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);
+ }
+ }
+}
diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java
index fb3700efe..7d6a00097 100644
--- a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java
+++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java
@@ -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(
diff --git a/java/com/android/dialer/protos/ProtoParsers.java b/java/com/android/dialer/protos/ProtoParsers.java
index e5292061b..00d5a26d2 100644
--- a/java/com/android/dialer/protos/ProtoParsers.java
+++ b/java/com/android/dialer/protos/ProtoParsers.java
@@ -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;
@@ -43,9 +44,26 @@ public final class ProtoParsers {
}
/**
+ * 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) {
@@ -57,6 +75,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.
*
* @throws RuntimeException if the proto cannot be parsed