/* * Copyright (C) 2017 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.database; import android.content.ContentValues; import android.database.Cursor; import android.database.MatrixCursor; import android.provider.CallLog.Calls; import android.support.annotation.NonNull; import android.support.annotation.WorkerThread; import android.telecom.PhoneAccountHandle; import android.text.TextUtils; import com.android.dialer.CoalescedIds; import com.android.dialer.DialerPhoneNumber; import com.android.dialer.NumberAttributes; import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.DataSources; import com.android.dialer.calllog.model.CoalescedRow; import com.android.dialer.common.Assert; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; import com.android.dialer.compat.telephony.TelephonyManagerCompat; import com.android.dialer.metrics.FutureTimer; import com.android.dialer.metrics.Metrics; import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; import com.android.dialer.telecom.TelecomUtil; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.protobuf.InvalidProtocolBufferException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import javax.inject.Inject; /** * Coalesces rows in {@link AnnotatedCallLog} by combining adjacent rows. * *
Applies the logic that determines which adjacent rows should be coalesced, and then delegates
* to each data source to determine how individual columns should be aggregated.
*/
public class Coalescer {
// Indexes for CoalescedAnnotatedCallLog.ALL_COLUMNS
private static final int ID = 0;
private static final int TIMESTAMP = 1;
private static final int NUMBER = 2;
private static final int FORMATTED_NUMBER = 3;
private static final int NUMBER_PRESENTATION = 4;
private static final int IS_READ = 5;
private static final int NEW = 6;
private static final int GEOCODED_LOCATION = 7;
private static final int PHONE_ACCOUNT_COMPONENT_NAME = 8;
private static final int PHONE_ACCOUNT_ID = 9;
private static final int FEATURES = 10;
private static final int NUMBER_ATTRIBUTES = 11;
private static final int IS_VOICEMAIL_CALL = 12;
private static final int VOICEMAIL_CALL_TAG = 13;
private static final int CALL_TYPE = 14;
private static final int COALESCED_IDS = 15;
private final DataSources dataSources;
private final FutureTimer futureTimer;
private final ListeningExecutorService backgroundExecutorService;
@Inject
Coalescer(
@BackgroundExecutor ListeningExecutorService backgroundExecutorService,
DataSources dataSources,
FutureTimer futureTimer) {
this.backgroundExecutorService = backgroundExecutorService;
this.dataSources = dataSources;
this.futureTimer = futureTimer;
}
/**
* Given rows from {@link AnnotatedCallLog}, combine adjacent ones which should be collapsed for
* display purposes.
*
* @param allAnnotatedCallLogRowsSortedByTimestampDesc {@link AnnotatedCallLog} rows sorted in
* descending order of timestamp.
* @return a future of a {@link MatrixCursor} containing the {@link CoalescedAnnotatedCallLog}
* rows to display
*/
public ListenableFuture The provided cursor should be one for {@link CoalescedAnnotatedCallLog}.
*/
public static CoalescedRow toRow(Cursor coalescedAnnotatedCallLogCursor) {
DialerPhoneNumber number;
try {
number = DialerPhoneNumber.parseFrom(coalescedAnnotatedCallLogCursor.getBlob(NUMBER));
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException("Couldn't parse DialerPhoneNumber bytes");
}
CoalescedIds coalescedIds;
try {
coalescedIds = CoalescedIds.parseFrom(coalescedAnnotatedCallLogCursor.getBlob(COALESCED_IDS));
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException("Couldn't parse CoalescedIds bytes");
}
NumberAttributes numberAttributes;
try {
numberAttributes =
NumberAttributes.parseFrom(coalescedAnnotatedCallLogCursor.getBlob(NUMBER_ATTRIBUTES));
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException("Couldn't parse NumberAttributes bytes");
}
CoalescedRow.Builder coalescedRowBuilder =
CoalescedRow.newBuilder()
.setId(coalescedAnnotatedCallLogCursor.getLong(ID))
.setTimestamp(coalescedAnnotatedCallLogCursor.getLong(TIMESTAMP))
.setNumber(number)
.setNumberPresentation(coalescedAnnotatedCallLogCursor.getInt(NUMBER_PRESENTATION))
.setIsRead(coalescedAnnotatedCallLogCursor.getInt(IS_READ) == 1)
.setIsNew(coalescedAnnotatedCallLogCursor.getInt(NEW) == 1)
.setFeatures(coalescedAnnotatedCallLogCursor.getInt(FEATURES))
.setCallType(coalescedAnnotatedCallLogCursor.getInt(CALL_TYPE))
.setNumberAttributes(numberAttributes)
.setIsVoicemailCall(coalescedAnnotatedCallLogCursor.getInt(IS_VOICEMAIL_CALL) == 1)
.setCoalescedIds(coalescedIds);
String formattedNumber = coalescedAnnotatedCallLogCursor.getString(FORMATTED_NUMBER);
if (!TextUtils.isEmpty(formattedNumber)) {
coalescedRowBuilder.setFormattedNumber(formattedNumber);
}
String geocodedLocation = coalescedAnnotatedCallLogCursor.getString(GEOCODED_LOCATION);
if (!TextUtils.isEmpty(geocodedLocation)) {
coalescedRowBuilder.setGeocodedLocation(geocodedLocation);
}
String phoneAccountComponentName =
coalescedAnnotatedCallLogCursor.getString(PHONE_ACCOUNT_COMPONENT_NAME);
if (!TextUtils.isEmpty(phoneAccountComponentName)) {
coalescedRowBuilder.setPhoneAccountComponentName(
coalescedAnnotatedCallLogCursor.getString(PHONE_ACCOUNT_COMPONENT_NAME));
}
String phoneAccountId = coalescedAnnotatedCallLogCursor.getString(PHONE_ACCOUNT_ID);
if (!TextUtils.isEmpty(phoneAccountId)) {
coalescedRowBuilder.setPhoneAccountId(phoneAccountId);
}
String voicemailCallTag = coalescedAnnotatedCallLogCursor.getString(VOICEMAIL_CALL_TAG);
if (!TextUtils.isEmpty(voicemailCallTag)) {
coalescedRowBuilder.setVoicemailCallTag(voicemailCallTag);
}
return coalescedRowBuilder.build();
}
/**
* Returns the timestamp at the provided cursor's current position.
*
* The provided cursor should be one for {@link CoalescedAnnotatedCallLog}.
*/
public static long getTimestamp(Cursor coalescedAnnotatedCallLogCursor) {
return coalescedAnnotatedCallLogCursor.getLong(TIMESTAMP);
}
}