• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
isLaunchIntent(Intent intent)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    */
newInstance( Context context, CallDetailsEntries details, DialerContact contact, boolean canReportCallerId, boolean canSupportAssistedDialing)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    */
newInstance( Context context, CoalescedIds coalescedAnnotatedCallLogIds, DialerContact contact, boolean canReportCallerId, boolean canSupportAssistedDialing)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
onCreate(Bundle savedInstanceState)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
onResume()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
onPause()186   protected void onPause() {
187     super.onPause();
188 
189     EnrichedCallComponent.get(this)
190         .getEnrichedCallManager()
191         .unregisterHistoricalDataChangedListener(enrichedCallHistoricalDataChangedListener);
192   }
193 
194   @Override
onNewIntent(Intent intent)195   protected void onNewIntent(Intent intent) {
196     super.onNewIntent(intent);
197     onHandleIntent(intent);
198   }
199 
onHandleIntent(Intent intent)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
onBackPressed()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 
CallDetailsLoaderCallbacks(CallDetailsActivity callDetailsActivity)253     CallDetailsLoaderCallbacks(CallDetailsActivity callDetailsActivity) {
254       this.activity = callDetailsActivity;
255     }
256 
257     @Override
onCreateLoader(int id, Bundle args)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
onLoadFinished(Loader<Cursor> loader, Cursor data)265     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
266       updateCallDetailsEntries(CallDetailsCursorLoader.toCallDetailsEntries(data));
267     }
268 
269     @Override
onLoaderReset(Loader<Cursor> loader)270     public void onLoaderReset(Loader<Cursor> loader) {
271       updateCallDetailsEntries(CallDetailsEntries.getDefaultInstance());
272     }
273 
updateCallDetailsEntries(CallDetailsEntries newEntries)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 
DeleteCallsTask( Activity activity, DialerContact contact, CallDetailsEntries callDetailsEntries)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)
doInBackground(Void... params)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
onPostExecute(Void result)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 
CallDetailsHeaderListener(CallDetailsActivity activity)353     CallDetailsHeaderListener(CallDetailsActivity activity) {
354       this.activityWeakReference = new WeakReference<>(activity);
355     }
356 
357     @Override
placeImsVideoCall(String phoneNumber)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
placeDuoVideoCall(String phoneNumber)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
placeVoiceCall(String phoneNumber, String postDialDigits)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 
getActivity()404     private CallDetailsActivity getActivity() {
405       return Preconditions.checkNotNull(activityWeakReference.get());
406     }
407 
408     @Override
openAssistedDialingSettings(View unused)409     public void openAssistedDialingSettings(View unused) {
410         Intent intent = new Intent(getActivity(), AssistedDialingSettingActivity.class);
411         getActivity().startActivity(intent);
412     }
413 
414     @Override
createAssistedDialerNumberParserTask( AssistedDialingNumberParseWorker worker, SuccessListener<Integer> successListener, FailureListener failureListener)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
doInBackground(@onNull String phoneNumber)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 
DeleteCallDetailsListener(CallDetailsActivity activity)456     DeleteCallDetailsListener(CallDetailsActivity activity) {
457       this.activityWeakReference = new WeakReference<>(activity);
458     }
459 
460     @Override
delete()461     public void delete() {
462       AsyncTaskExecutors.createAsyncTaskExecutor()
463           .submit(
464               ASYNC_TASK_ID,
465               new DeleteCallsTask(getActivity(), getActivity().contact, getActivity().entries));
466     }
467 
getActivity()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 
ReportCallIdListener(Activity activity)477     ReportCallIdListener(Activity activity) {
478       this.activityWeakReference = new WeakReference<>(activity);
479     }
480 
481     @Override
reportCallId(String number)482     public void reportCallId(String number) {
483       ReportDialogFragment.newInstance(number)
484           .show(getActivity().getFragmentManager(), null /* tag */);
485     }
486 
487     @Override
canReportCallerId(String number)488     public boolean canReportCallerId(String number) {
489       return getActivity().getIntent().getExtras().getBoolean(EXTRA_CAN_REPORT_CALLER_ID, false);
490     }
491 
getActivity()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 
EnrichedCallHistoricalDataChangedListener(CallDetailsActivity activity)501     EnrichedCallHistoricalDataChangedListener(CallDetailsActivity activity) {
502       this.activityWeakReference = new WeakReference<>(activity);
503     }
504 
505     @Override
onHistoricalDataChanged()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 
getActivity()517     private CallDetailsActivity getActivity() {
518       return Preconditions.checkNotNull(activityWeakReference.get());
519     }
520 
521     @NonNull
getAllHistoricalData( @ullable String number, @NonNull CallDetailsEntries entries)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 
generateAndMapNewCallDetailsEntriesHistoryResults( @ullable String number, @NonNull CallDetailsEntries callDetailsEntries, @NonNull Map<CallDetailsEntry, List<HistoryResult>> mappedResults)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