diff options
Diffstat (limited to 'java/com/android/dialer/calllog')
-rw-r--r-- | java/com/android/dialer/calllog/CallLogCacheUpdater.java | 129 | ||||
-rw-r--r-- | java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java | 12 |
2 files changed, 141 insertions, 0 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( |