Home | History | Annotate | Download | only in calldetails
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dialer.calldetails;
     18 
     19 import android.Manifest.permission;
     20 import android.annotation.SuppressLint;
     21 import android.app.Activity;
     22 import android.app.LoaderManager.LoaderCallbacks;
     23 import android.content.ActivityNotFoundException;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.Loader;
     27 import android.database.Cursor;
     28 import android.os.AsyncTask;
     29 import android.os.Bundle;
     30 import android.provider.CallLog;
     31 import android.provider.CallLog.Calls;
     32 import android.support.annotation.NonNull;
     33 import android.support.annotation.Nullable;
     34 import android.support.annotation.RequiresPermission;
     35 import android.support.v7.app.AppCompatActivity;
     36 import android.support.v7.widget.LinearLayoutManager;
     37 import android.support.v7.widget.RecyclerView;
     38 import android.support.v7.widget.Toolbar;
     39 import android.view.View;
     40 import android.widget.Toast;
     41 import com.android.dialer.CoalescedIds;
     42 import com.android.dialer.assisteddialing.ui.AssistedDialingSettingActivity;
     43 import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
     44 import com.android.dialer.callintent.CallInitiationType;
     45 import com.android.dialer.callintent.CallIntentBuilder;
     46 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
     47 import com.android.dialer.common.Assert;
     48 import com.android.dialer.common.LogUtil;
     49 import com.android.dialer.common.concurrent.AsyncTaskExecutors;
     50 import com.android.dialer.common.concurrent.DialerExecutor.FailureListener;
     51 import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener;
     52 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
     53 import com.android.dialer.common.concurrent.DialerExecutorComponent;
     54 import com.android.dialer.constants.ActivityRequestCodes;
     55 import com.android.dialer.dialercontact.DialerContact;
     56 import com.android.dialer.duo.Duo;
     57 import com.android.dialer.duo.DuoComponent;
     58 import com.android.dialer.enrichedcall.EnrichedCallComponent;
     59 import com.android.dialer.enrichedcall.EnrichedCallManager;
     60 import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult;
     61 import com.android.dialer.logging.DialerImpression;
     62 import com.android.dialer.logging.Logger;
     63 import com.android.dialer.logging.UiAction;
     64 import com.android.dialer.performancereport.PerformanceReport;
     65 import com.android.dialer.postcall.PostCall;
     66 import com.android.dialer.precall.PreCall;
     67 import com.android.dialer.protos.ProtoParsers;
     68 import com.google.common.base.Optional;
     69 import com.google.common.base.Preconditions;
     70 import com.google.i18n.phonenumbers.NumberParseException;
     71 import com.google.i18n.phonenumbers.PhoneNumberUtil;
     72 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
     73 import java.lang.ref.WeakReference;
     74 import java.util.Collections;
     75 import java.util.List;
     76 import java.util.Map;
     77 
     78 /** Displays the details of a specific call log entry. */
     79 public class CallDetailsActivity extends AppCompatActivity {
     80   private static final int CALL_DETAILS_LOADER_ID = 0;
     81 
     82   public static final String EXTRA_PHONE_NUMBER = "phone_number";
     83   public static final String EXTRA_HAS_ENRICHED_CALL_DATA = "has_enriched_call_data";
     84   public static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries";
     85   public static final String EXTRA_COALESCED_CALL_LOG_IDS = "coalesced_call_log_ids";
     86   public static final String EXTRA_CONTACT = "contact";
     87   public static final String EXTRA_CAN_REPORT_CALLER_ID = "can_report_caller_id";
     88   public static final String EXTRA_CAN_SUPPORT_ASSISTED_DIALING = "can_support_assisted_dialing";
     89 
     90   private final CallDetailsHeaderViewHolder.CallDetailsHeaderListener callDetailsHeaderListener =
     91       new CallDetailsHeaderListener(this);
     92   private final CallDetailsFooterViewHolder.DeleteCallDetailsListener deleteCallDetailsListener =
     93       new DeleteCallDetailsListener(this);
     94   private final CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener =
     95       new ReportCallIdListener(this);
     96   private final EnrichedCallManager.HistoricalDataChangedListener
     97       enrichedCallHistoricalDataChangedListener =
     98           new EnrichedCallHistoricalDataChangedListener(this);
     99 
    100   private CallDetailsEntries entries;
    101   private DialerContact contact;
    102   private CallDetailsAdapter adapter;
    103 
    104   // This will be present only when the activity is launched from the new call log UI, i.e., a list
    105   // of coalesced annotated call log IDs is included in the intent.
    106   private Optional<CoalescedIds> coalescedCallLogIds = Optional.absent();
    107 
    108   public static boolean isLaunchIntent(Intent intent) {
    109     return intent.getComponent() != null
    110         && CallDetailsActivity.class.getName().equals(intent.getComponent().getClassName());
    111   }
    112 
    113   /**
    114    * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the old call log
    115    * UI.
    116    */
    117   public static Intent newInstance(
    118       Context context,
    119       CallDetailsEntries details,
    120       DialerContact contact,
    121       boolean canReportCallerId,
    122       boolean canSupportAssistedDialing) {
    123     Intent intent = new Intent(context, CallDetailsActivity.class);
    124     ProtoParsers.put(intent, EXTRA_CONTACT, Assert.isNotNull(contact));
    125     ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, Assert.isNotNull(details));
    126     intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId);
    127     intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing);
    128     return intent;
    129   }
    130 
    131   /**
    132    * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the new call log
    133    * UI.
    134    */
    135   public static Intent newInstance(
    136       Context context,
    137       CoalescedIds coalescedAnnotatedCallLogIds,
    138       DialerContact contact,
    139       boolean canReportCallerId,
    140       boolean canSupportAssistedDialing) {
    141     Intent intent = new Intent(context, CallDetailsActivity.class);
    142     ProtoParsers.put(intent, EXTRA_CONTACT, Assert.isNotNull(contact));
    143     ProtoParsers.put(
    144         intent, EXTRA_COALESCED_CALL_LOG_IDS, Assert.isNotNull(coalescedAnnotatedCallLogIds));
    145     intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId);
    146     intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing);
    147     return intent;
    148   }
    149 
    150   @Override
    151   protected void onCreate(Bundle savedInstanceState) {
    152     super.onCreate(savedInstanceState);
    153     setContentView(R.layout.call_details_activity);
    154     Toolbar toolbar = findViewById(R.id.toolbar);
    155     toolbar.setTitle(R.string.call_details);
    156     toolbar.setNavigationOnClickListener(
    157         v -> {
    158           PerformanceReport.recordClick(UiAction.Type.CLOSE_CALL_DETAIL_WITH_CANCEL_BUTTON);
    159           finish();
    160         });
    161     onHandleIntent(getIntent());
    162   }
    163 
    164   @Override
    165   protected void onResume() {
    166     super.onResume();
    167 
    168     // Some calls may not be recorded (eg. from quick contact),
    169     // so we should restart recording after these calls. (Recorded call is stopped)
    170     PostCall.restartPerformanceRecordingIfARecentCallExist(this);
    171     if (!PerformanceReport.isRecording()) {
    172       PerformanceReport.startRecording();
    173     }
    174 
    175     PostCall.promptUserForMessageIfNecessary(this, findViewById(R.id.recycler_view));
    176 
    177     EnrichedCallComponent.get(this)
    178         .getEnrichedCallManager()
    179         .registerHistoricalDataChangedListener(enrichedCallHistoricalDataChangedListener);
    180     EnrichedCallComponent.get(this)
    181         .getEnrichedCallManager()
    182         .requestAllHistoricalData(contact.getNumber(), entries);
    183   }
    184 
    185   @Override
    186   protected void onPause() {
    187     super.onPause();
    188 
    189     EnrichedCallComponent.get(this)
    190         .getEnrichedCallManager()
    191         .unregisterHistoricalDataChangedListener(enrichedCallHistoricalDataChangedListener);
    192   }
    193 
    194   @Override
    195   protected void onNewIntent(Intent intent) {
    196     super.onNewIntent(intent);
    197     onHandleIntent(intent);
    198   }
    199 
    200   private void onHandleIntent(Intent intent) {
    201     boolean hasCallDetailsEntries = intent.hasExtra(EXTRA_CALL_DETAILS_ENTRIES);
    202     boolean hasCoalescedCallLogIds = intent.hasExtra(EXTRA_COALESCED_CALL_LOG_IDS);
    203     Assert.checkArgument(
    204         (hasCallDetailsEntries && !hasCoalescedCallLogIds)
    205             || (!hasCallDetailsEntries && hasCoalescedCallLogIds),
    206         "One and only one of EXTRA_CALL_DETAILS_ENTRIES and EXTRA_COALESCED_CALL_LOG_IDS "
    207             + "can be included in the intent.");
    208 
    209     contact = ProtoParsers.getTrusted(intent, EXTRA_CONTACT, DialerContact.getDefaultInstance());
    210     if (hasCallDetailsEntries) {
    211       entries =
    212           ProtoParsers.getTrusted(
    213               intent, EXTRA_CALL_DETAILS_ENTRIES, CallDetailsEntries.getDefaultInstance());
    214     } else {
    215       entries = CallDetailsEntries.getDefaultInstance();
    216       coalescedCallLogIds =
    217           Optional.of(
    218               ProtoParsers.getTrusted(
    219                   intent, EXTRA_COALESCED_CALL_LOG_IDS, CoalescedIds.getDefaultInstance()));
    220       getLoaderManager()
    221           .initLoader(
    222               CALL_DETAILS_LOADER_ID, /* args = */ null, new CallDetailsLoaderCallbacks(this));
    223     }
    224 
    225     adapter =
    226         new CallDetailsAdapter(
    227             this /* context */,
    228             contact,
    229             entries.getEntriesList(),
    230             callDetailsHeaderListener,
    231             reportCallIdListener,
    232             deleteCallDetailsListener);
    233 
    234     RecyclerView recyclerView = findViewById(R.id.recycler_view);
    235     recyclerView.setLayoutManager(new LinearLayoutManager(this));
    236     recyclerView.setAdapter(adapter);
    237     PerformanceReport.logOnScrollStateChange(recyclerView);
    238   }
    239 
    240   @Override
    241   public void onBackPressed() {
    242     PerformanceReport.recordClick(UiAction.Type.PRESS_ANDROID_BACK_BUTTON);
    243     super.onBackPressed();
    244   }
    245 
    246   /**
    247    * {@link LoaderCallbacks} for {@link CallDetailsCursorLoader}, which loads call detail entries
    248    * from {@link AnnotatedCallLog}.
    249    */
    250   private static final class CallDetailsLoaderCallbacks implements LoaderCallbacks<Cursor> {
    251     private final CallDetailsActivity activity;
    252 
    253     CallDetailsLoaderCallbacks(CallDetailsActivity callDetailsActivity) {
    254       this.activity = callDetailsActivity;
    255     }
    256 
    257     @Override
    258     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    259       Assert.checkState(activity.coalescedCallLogIds.isPresent());
    260 
    261       return new CallDetailsCursorLoader(activity, activity.coalescedCallLogIds.get());
    262     }
    263 
    264     @Override
    265     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    266       updateCallDetailsEntries(CallDetailsCursorLoader.toCallDetailsEntries(data));
    267     }
    268 
    269     @Override
    270     public void onLoaderReset(Loader<Cursor> loader) {
    271       updateCallDetailsEntries(CallDetailsEntries.getDefaultInstance());
    272     }
    273 
    274     private void updateCallDetailsEntries(CallDetailsEntries newEntries) {
    275       activity.entries = newEntries;
    276       activity.adapter.updateCallDetailsEntries(newEntries.getEntriesList());
    277       EnrichedCallComponent.get(activity)
    278           .getEnrichedCallManager()
    279           .requestAllHistoricalData(activity.contact.getNumber(), newEntries);
    280     }
    281   }
    282 
    283   /** Delete specified calls from the call log. */
    284   private static class DeleteCallsTask extends AsyncTask<Void, Void, Void> {
    285     // Use a weak reference to hold the Activity so that there is no memory leak.
    286     private final WeakReference<Activity> activityWeakReference;
    287 
    288     private final DialerContact contact;
    289     private final CallDetailsEntries callDetailsEntries;
    290     private final String callIds;
    291 
    292     DeleteCallsTask(
    293         Activity activity, DialerContact contact, CallDetailsEntries callDetailsEntries) {
    294       this.activityWeakReference = new WeakReference<>(activity);
    295       this.contact = contact;
    296       this.callDetailsEntries = callDetailsEntries;
    297 
    298       StringBuilder callIds = new StringBuilder();
    299       for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) {
    300         if (callIds.length() != 0) {
    301           callIds.append(",");
    302         }
    303         callIds.append(entry.getCallId());
    304       }
    305       this.callIds = callIds.toString();
    306     }
    307 
    308     @Override
    309     // Suppress the lint check here as the user will not be able to see call log entries if
    310     // permission.WRITE_CALL_LOG is not granted.
    311     @SuppressLint("MissingPermission")
    312     @RequiresPermission(value = permission.WRITE_CALL_LOG)
    313     protected Void doInBackground(Void... params) {
    314       Activity activity = activityWeakReference.get();
    315       if (activity == null) {
    316         return null;
    317       }
    318 
    319       activity
    320           .getContentResolver()
    321           .delete(
    322               Calls.CONTENT_URI,
    323               CallLog.Calls._ID + " IN (" + callIds + ")" /* where */,
    324               null /* selectionArgs */);
    325       return null;
    326     }
    327 
    328     @Override
    329     public void onPostExecute(Void result) {
    330       Activity activity = activityWeakReference.get();
    331       if (activity == null) {
    332         return;
    333       }
    334 
    335       Intent data = new Intent();
    336       data.putExtra(EXTRA_PHONE_NUMBER, contact.getNumber());
    337       for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) {
    338         if (entry.getHistoryResultsCount() > 0) {
    339           data.putExtra(EXTRA_HAS_ENRICHED_CALL_DATA, true);
    340           break;
    341         }
    342       }
    343 
    344       activity.setResult(RESULT_OK, data);
    345       activity.finish();
    346     }
    347   }
    348 
    349   private static final class CallDetailsHeaderListener
    350       implements CallDetailsHeaderViewHolder.CallDetailsHeaderListener {
    351     private final WeakReference<CallDetailsActivity> activityWeakReference;
    352 
    353     CallDetailsHeaderListener(CallDetailsActivity activity) {
    354       this.activityWeakReference = new WeakReference<>(activity);
    355     }
    356 
    357     @Override
    358     public void placeImsVideoCall(String phoneNumber) {
    359       Logger.get(getActivity())
    360           .logImpression(DialerImpression.Type.CALL_DETAILS_IMS_VIDEO_CALL_BACK);
    361       PreCall.start(
    362           getActivity(),
    363           new CallIntentBuilder(phoneNumber, CallInitiationType.Type.CALL_DETAILS)
    364               .setIsVideoCall(true));
    365     }
    366 
    367     @Override
    368     public void placeDuoVideoCall(String phoneNumber) {
    369       Logger.get(getActivity())
    370           .logImpression(DialerImpression.Type.CALL_DETAILS_LIGHTBRINGER_CALL_BACK);
    371       Duo duo = DuoComponent.get(getActivity()).getDuo();
    372       if (!duo.isReachable(getActivity(), phoneNumber)) {
    373         placeImsVideoCall(phoneNumber);
    374         return;
    375       }
    376 
    377       try {
    378         getActivity()
    379             .startActivityForResult(
    380                 duo.getIntent(getActivity(), phoneNumber), ActivityRequestCodes.DIALTACTS_DUO);
    381       } catch (ActivityNotFoundException e) {
    382         Toast.makeText(getActivity(), R.string.activity_not_available, Toast.LENGTH_SHORT).show();
    383       }
    384     }
    385 
    386     @Override
    387     public void placeVoiceCall(String phoneNumber, String postDialDigits) {
    388       Logger.get(getActivity()).logImpression(DialerImpression.Type.CALL_DETAILS_VOICE_CALL_BACK);
    389 
    390       boolean canSupportedAssistedDialing =
    391           getActivity()
    392               .getIntent()
    393               .getExtras()
    394               .getBoolean(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, false);
    395       CallIntentBuilder callIntentBuilder =
    396           new CallIntentBuilder(phoneNumber + postDialDigits, CallInitiationType.Type.CALL_DETAILS);
    397       if (canSupportedAssistedDialing) {
    398         callIntentBuilder.setAllowAssistedDial(true);
    399       }
    400 
    401       PreCall.start(getActivity(), callIntentBuilder);
    402     }
    403 
    404     private CallDetailsActivity getActivity() {
    405       return Preconditions.checkNotNull(activityWeakReference.get());
    406     }
    407 
    408     @Override
    409     public void openAssistedDialingSettings(View unused) {
    410         Intent intent = new Intent(getActivity(), AssistedDialingSettingActivity.class);
    411         getActivity().startActivity(intent);
    412     }
    413 
    414     @Override
    415     public void createAssistedDialerNumberParserTask(
    416         AssistedDialingNumberParseWorker worker,
    417         SuccessListener<Integer> successListener,
    418         FailureListener failureListener) {
    419       DialerExecutorComponent.get(getActivity().getApplicationContext())
    420           .dialerExecutorFactory()
    421           .createUiTaskBuilder(
    422               getActivity().getFragmentManager(),
    423               "CallDetailsActivity.createAssistedDialerNumberParserTask",
    424               new AssistedDialingNumberParseWorker())
    425           .onSuccess(successListener)
    426           .onFailure(failureListener)
    427           .build()
    428           .executeParallel(getActivity().contact.getNumber());
    429     }
    430   }
    431 
    432   static class AssistedDialingNumberParseWorker implements Worker<String, Integer> {
    433 
    434     @Override
    435     public Integer doInBackground(@NonNull String phoneNumber) {
    436       PhoneNumber parsedNumber = null;
    437       try {
    438         parsedNumber = PhoneNumberUtil.getInstance().parse(phoneNumber, null);
    439       } catch (NumberParseException e) {
    440         LogUtil.w(
    441             "AssistedDialingNumberParseWorker.doInBackground",
    442             "couldn't parse phone number: " + LogUtil.sanitizePii(phoneNumber),
    443             e);
    444         return 0;
    445       }
    446       return parsedNumber.getCountryCode();
    447     }
    448   }
    449 
    450   private static final class DeleteCallDetailsListener
    451       implements CallDetailsFooterViewHolder.DeleteCallDetailsListener {
    452     private static final String ASYNC_TASK_ID = "task_delete";
    453 
    454     private final WeakReference<CallDetailsActivity> activityWeakReference;
    455 
    456     DeleteCallDetailsListener(CallDetailsActivity activity) {
    457       this.activityWeakReference = new WeakReference<>(activity);
    458     }
    459 
    460     @Override
    461     public void delete() {
    462       AsyncTaskExecutors.createAsyncTaskExecutor()
    463           .submit(
    464               ASYNC_TASK_ID,
    465               new DeleteCallsTask(getActivity(), getActivity().contact, getActivity().entries));
    466     }
    467 
    468     private CallDetailsActivity getActivity() {
    469       return Preconditions.checkNotNull(activityWeakReference.get());
    470     }
    471   }
    472 
    473   private static final class ReportCallIdListener
    474       implements CallDetailsFooterViewHolder.ReportCallIdListener {
    475     private final WeakReference<Activity> activityWeakReference;
    476 
    477     ReportCallIdListener(Activity activity) {
    478       this.activityWeakReference = new WeakReference<>(activity);
    479     }
    480 
    481     @Override
    482     public void reportCallId(String number) {
    483       ReportDialogFragment.newInstance(number)
    484           .show(getActivity().getFragmentManager(), null /* tag */);
    485     }
    486 
    487     @Override
    488     public boolean canReportCallerId(String number) {
    489       return getActivity().getIntent().getExtras().getBoolean(EXTRA_CAN_REPORT_CALLER_ID, false);
    490     }
    491 
    492     private Activity getActivity() {
    493       return Preconditions.checkNotNull(activityWeakReference.get());
    494     }
    495   }
    496 
    497   private static final class EnrichedCallHistoricalDataChangedListener
    498       implements EnrichedCallManager.HistoricalDataChangedListener {
    499     private final WeakReference<CallDetailsActivity> activityWeakReference;
    500 
    501     EnrichedCallHistoricalDataChangedListener(CallDetailsActivity activity) {
    502       this.activityWeakReference = new WeakReference<>(activity);
    503     }
    504 
    505     @Override
    506     public void onHistoricalDataChanged() {
    507       CallDetailsActivity activity = getActivity();
    508       Map<CallDetailsEntry, List<HistoryResult>> mappedResults =
    509           getAllHistoricalData(activity.contact.getNumber(), activity.entries);
    510 
    511       activity.adapter.updateCallDetailsEntries(
    512           generateAndMapNewCallDetailsEntriesHistoryResults(
    513                   activity.contact.getNumber(), activity.entries, mappedResults)
    514               .getEntriesList());
    515     }
    516 
    517     private CallDetailsActivity getActivity() {
    518       return Preconditions.checkNotNull(activityWeakReference.get());
    519     }
    520 
    521     @NonNull
    522     private Map<CallDetailsEntry, List<HistoryResult>> getAllHistoricalData(
    523         @Nullable String number, @NonNull CallDetailsEntries entries) {
    524       if (number == null) {
    525         return Collections.emptyMap();
    526       }
    527 
    528       Map<CallDetailsEntry, List<HistoryResult>> historicalData =
    529           EnrichedCallComponent.get(getActivity())
    530               .getEnrichedCallManager()
    531               .getAllHistoricalData(number, entries);
    532       if (historicalData == null) {
    533         return Collections.emptyMap();
    534       }
    535       return historicalData;
    536     }
    537 
    538     private static CallDetailsEntries generateAndMapNewCallDetailsEntriesHistoryResults(
    539         @Nullable String number,
    540         @NonNull CallDetailsEntries callDetailsEntries,
    541         @NonNull Map<CallDetailsEntry, List<HistoryResult>> mappedResults) {
    542       if (number == null) {
    543         return callDetailsEntries;
    544       }
    545       CallDetailsEntries.Builder mutableCallDetailsEntries = CallDetailsEntries.newBuilder();
    546       for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) {
    547         CallDetailsEntry.Builder newEntry = CallDetailsEntry.newBuilder().mergeFrom(entry);
    548         List<HistoryResult> results = mappedResults.get(entry);
    549         if (results != null) {
    550           newEntry.addAllHistoryResults(mappedResults.get(entry));
    551           LogUtil.v(
    552               "CallDetailsActivity.generateAndMapNewCallDetailsEntriesHistoryResults",
    553               "mapped %d results",
    554               newEntry.getHistoryResultsList().size());
    555         }
    556         mutableCallDetailsEntries.addEntries(newEntry.build());
    557       }
    558       return mutableCallDetailsEntries.build();
    559     }
    560   }
    561 }
    562