From 664284702162f44c681fac95d95a97a3f1711490 Mon Sep 17 00:00:00 2001 From: linyuh Date: Wed, 14 Mar 2018 15:21:01 -0700 Subject: Split CallDetailsActivity Bug: 74202944 Test: Existing tests PiperOrigin-RevId: 189095432 Change-Id: I13015ee6c5767edd95b78c3fb2e7338495e9e1d7 --- .../android/dialer/calldetails/AndroidManifest.xml | 8 +- .../dialer/calldetails/CallDetailsActivity.java | 513 ++------------------- .../calldetails/CallDetailsActivityCommon.java | 466 +++++++++++++++++++ .../dialer/calldetails/CallDetailsAdapter.java | 120 ++--- .../calldetails/CallDetailsAdapterCommon.java | 152 ++++++ .../calldetails/CallDetailsCursorLoader.java | 4 +- .../calldetails/CallDetailsEntryViewHolder.java | 2 +- .../calldetails/CallDetailsFooterViewHolder.java | 5 +- .../calldetails/CallDetailsHeaderViewHolder.java | 32 +- .../dialer/calldetails/OldCallDetailsActivity.java | 94 ++++ .../dialer/calldetails/OldCallDetailsAdapter.java | 73 +++ .../dialer/calldetails/ReportDialogFragment.java | 2 +- 12 files changed, 895 insertions(+), 576 deletions(-) create mode 100644 java/com/android/dialer/calldetails/CallDetailsActivityCommon.java create mode 100644 java/com/android/dialer/calldetails/CallDetailsAdapterCommon.java create mode 100644 java/com/android/dialer/calldetails/OldCallDetailsActivity.java create mode 100644 java/com/android/dialer/calldetails/OldCallDetailsAdapter.java (limited to 'java/com/android/dialer/calldetails') diff --git a/java/com/android/dialer/calldetails/AndroidManifest.xml b/java/com/android/dialer/calldetails/AndroidManifest.xml index 9cf656e42..9ef05a380 100644 --- a/java/com/android/dialer/calldetails/AndroidManifest.xml +++ b/java/com/android/dialer/calldetails/AndroidManifest.xml @@ -20,8 +20,14 @@ + + diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java index ec124df9e..672043b6e 100644 --- a/java/com/android/dialer/calldetails/CallDetailsActivity.java +++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java @@ -16,122 +16,42 @@ package com.android.dialer.calldetails; -import android.Manifest.permission; -import android.annotation.SuppressLint; -import android.app.Activity; import android.app.LoaderManager.LoaderCallbacks; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.Loader; import android.database.Cursor; -import android.os.AsyncTask; import android.os.Bundle; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresPermission; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.view.View; -import android.widget.Toast; import com.android.dialer.CoalescedIds; -import com.android.dialer.assisteddialing.ui.AssistedDialingSettingActivity; -import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; -import com.android.dialer.callintent.CallInitiationType; -import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.calldetails.CallDetailsFooterViewHolder.DeleteCallDetailsListener; +import com.android.dialer.calldetails.CallDetailsFooterViewHolder.ReportCallIdListener; +import com.android.dialer.calldetails.CallDetailsHeaderViewHolder.CallDetailsHeaderListener; import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; import com.android.dialer.common.Assert; -import com.android.dialer.common.LogUtil; -import com.android.dialer.common.concurrent.AsyncTaskExecutors; -import com.android.dialer.common.concurrent.DialerExecutor.FailureListener; -import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; -import com.android.dialer.common.concurrent.DialerExecutor.Worker; -import com.android.dialer.common.concurrent.DialerExecutorComponent; -import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.dialercontact.DialerContact; -import com.android.dialer.duo.Duo; -import com.android.dialer.duo.DuoComponent; import com.android.dialer.enrichedcall.EnrichedCallComponent; -import com.android.dialer.enrichedcall.EnrichedCallManager; -import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult; -import com.android.dialer.logging.DialerImpression; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.UiAction; -import com.android.dialer.performancereport.PerformanceReport; -import com.android.dialer.postcall.PostCall; -import com.android.dialer.precall.PreCall; import com.android.dialer.protos.ProtoParsers; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.i18n.phonenumbers.NumberParseException; -import com.google.i18n.phonenumbers.PhoneNumberUtil; -import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; -import java.lang.ref.WeakReference; -import java.util.Collections; -import java.util.List; -import java.util.Map; -/** Displays the details of a specific call log entry. */ -public class CallDetailsActivity extends AppCompatActivity { - private static final int CALL_DETAILS_LOADER_ID = 0; - - public static final String EXTRA_PHONE_NUMBER = "phone_number"; - public static final String EXTRA_HAS_ENRICHED_CALL_DATA = "has_enriched_call_data"; - public static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries"; +/** + * Displays the details of a specific call log entry. + * + *

This activity is for the new call log. + * + *

See {@link CallDetailsAdapterCommon} for logic shared between this activity and the one for + * the old call log. + */ +public final class CallDetailsActivity extends CallDetailsActivityCommon { public static final String EXTRA_COALESCED_CALL_LOG_IDS = "coalesced_call_log_ids"; public static final String EXTRA_CONTACT = "contact"; - public static final String EXTRA_CAN_REPORT_CALLER_ID = "can_report_caller_id"; - public static final String EXTRA_CAN_SUPPORT_ASSISTED_DIALING = "can_support_assisted_dialing"; - - private final CallDetailsHeaderViewHolder.CallDetailsHeaderListener callDetailsHeaderListener = - new CallDetailsHeaderListener(this); - private final CallDetailsFooterViewHolder.DeleteCallDetailsListener deleteCallDetailsListener = - new DeleteCallDetailsListener(this); - private final CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener = - new ReportCallIdListener(this); - private final EnrichedCallManager.HistoricalDataChangedListener - enrichedCallHistoricalDataChangedListener = - new EnrichedCallHistoricalDataChangedListener(this); - - private CallDetailsEntries entries; - private DialerContact contact; - private CallDetailsAdapter adapter; - // This will be present only when the activity is launched from the new call log UI, i.e., a list - // of coalesced annotated call log IDs is included in the intent. - private Optional coalescedCallLogIds = Optional.absent(); + private static final int CALL_DETAILS_LOADER_ID = 0; - public static boolean isLaunchIntent(Intent intent) { - return intent.getComponent() != null - && CallDetailsActivity.class.getName().equals(intent.getComponent().getClassName()); - } + /** IDs of call log entries, used to retrieve them from the annotated call log. */ + private CoalescedIds coalescedCallLogIds; - /** - * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the old call log - * UI. - */ - public static Intent newInstance( - Context context, - CallDetailsEntries details, - DialerContact contact, - boolean canReportCallerId, - boolean canSupportAssistedDialing) { - Intent intent = new Intent(context, CallDetailsActivity.class); - ProtoParsers.put(intent, EXTRA_CONTACT, Assert.isNotNull(contact)); - ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, Assert.isNotNull(details)); - intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId); - intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing); - return intent; - } + private DialerContact contact; - /** - * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the new call log - * UI. - */ + /** Returns an {@link Intent} to launch this activity. */ public static Intent newInstance( Context context, CoalescedIds coalescedAnnotatedCallLogIds, @@ -148,99 +68,39 @@ public class CallDetailsActivity extends AppCompatActivity { } @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.call_details_activity); - Toolbar toolbar = findViewById(R.id.toolbar); - toolbar.setTitle(R.string.call_details); - toolbar.setNavigationOnClickListener( - v -> { - PerformanceReport.recordClick(UiAction.Type.CLOSE_CALL_DETAIL_WITH_CANCEL_BUTTON); - finish(); - }); - onHandleIntent(getIntent()); - } - - @Override - protected void onResume() { - super.onResume(); - - // Some calls may not be recorded (eg. from quick contact), - // so we should restart recording after these calls. (Recorded call is stopped) - PostCall.restartPerformanceRecordingIfARecentCallExist(this); - if (!PerformanceReport.isRecording()) { - PerformanceReport.startRecording(); - } - - PostCall.promptUserForMessageIfNecessary(this, findViewById(R.id.recycler_view)); + protected void handleIntent(Intent intent) { + Assert.checkArgument(intent.hasExtra(EXTRA_COALESCED_CALL_LOG_IDS)); + Assert.checkArgument(intent.hasExtra(EXTRA_CAN_REPORT_CALLER_ID)); + Assert.checkArgument(intent.hasExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING)); - EnrichedCallComponent.get(this) - .getEnrichedCallManager() - .registerHistoricalDataChangedListener(enrichedCallHistoricalDataChangedListener); - EnrichedCallComponent.get(this) - .getEnrichedCallManager() - .requestAllHistoricalData(contact.getNumber(), entries); - } - - @Override - protected void onPause() { - super.onPause(); + contact = ProtoParsers.getTrusted(intent, EXTRA_CONTACT, DialerContact.getDefaultInstance()); + setCallDetailsEntries(CallDetailsEntries.getDefaultInstance()); + coalescedCallLogIds = + ProtoParsers.getTrusted( + intent, EXTRA_COALESCED_CALL_LOG_IDS, CoalescedIds.getDefaultInstance()); - EnrichedCallComponent.get(this) - .getEnrichedCallManager() - .unregisterHistoricalDataChangedListener(enrichedCallHistoricalDataChangedListener); + getLoaderManager() + .initLoader( + CALL_DETAILS_LOADER_ID, /* args = */ null, new CallDetailsLoaderCallbacks(this)); } @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - onHandleIntent(intent); - } - - private void onHandleIntent(Intent intent) { - boolean hasCallDetailsEntries = intent.hasExtra(EXTRA_CALL_DETAILS_ENTRIES); - boolean hasCoalescedCallLogIds = intent.hasExtra(EXTRA_COALESCED_CALL_LOG_IDS); - Assert.checkArgument( - (hasCallDetailsEntries && !hasCoalescedCallLogIds) - || (!hasCallDetailsEntries && hasCoalescedCallLogIds), - "One and only one of EXTRA_CALL_DETAILS_ENTRIES and EXTRA_COALESCED_CALL_LOG_IDS " - + "can be included in the intent."); - - contact = ProtoParsers.getTrusted(intent, EXTRA_CONTACT, DialerContact.getDefaultInstance()); - if (hasCallDetailsEntries) { - entries = - ProtoParsers.getTrusted( - intent, EXTRA_CALL_DETAILS_ENTRIES, CallDetailsEntries.getDefaultInstance()); - } else { - entries = CallDetailsEntries.getDefaultInstance(); - coalescedCallLogIds = - Optional.of( - ProtoParsers.getTrusted( - intent, EXTRA_COALESCED_CALL_LOG_IDS, CoalescedIds.getDefaultInstance())); - getLoaderManager() - .initLoader( - CALL_DETAILS_LOADER_ID, /* args = */ null, new CallDetailsLoaderCallbacks(this)); - } - - adapter = - new CallDetailsAdapter( - this /* context */, - contact, - entries.getEntriesList(), - callDetailsHeaderListener, - reportCallIdListener, - deleteCallDetailsListener); - - RecyclerView recyclerView = findViewById(R.id.recycler_view); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.setAdapter(adapter); - PerformanceReport.logOnScrollStateChange(recyclerView); + protected CallDetailsAdapterCommon createAdapter( + CallDetailsHeaderListener callDetailsHeaderListener, + ReportCallIdListener reportCallIdListener, + DeleteCallDetailsListener deleteCallDetailsListener) { + return new CallDetailsAdapter( + this, + contact, + getCallDetailsEntries(), + callDetailsHeaderListener, + reportCallIdListener, + deleteCallDetailsListener); } @Override - public void onBackPressed() { - PerformanceReport.recordClick(UiAction.Type.PRESS_ANDROID_BACK_BUTTON); - super.onBackPressed(); + protected String getNumber() { + return contact.getNumber(); } /** @@ -256,9 +116,7 @@ public class CallDetailsActivity extends AppCompatActivity { @Override public Loader onCreateLoader(int id, Bundle args) { - Assert.checkState(activity.coalescedCallLogIds.isPresent()); - - return new CallDetailsCursorLoader(activity, activity.coalescedCallLogIds.get()); + return new CallDetailsCursorLoader(activity, Assert.isNotNull(activity.coalescedCallLogIds)); } @Override @@ -272,290 +130,11 @@ public class CallDetailsActivity extends AppCompatActivity { } private void updateCallDetailsEntries(CallDetailsEntries newEntries) { - activity.entries = newEntries; - activity.adapter.updateCallDetailsEntries(newEntries.getEntriesList()); + activity.setCallDetailsEntries(newEntries); + activity.getAdapter().updateCallDetailsEntries(newEntries); EnrichedCallComponent.get(activity) .getEnrichedCallManager() - .requestAllHistoricalData(activity.contact.getNumber(), newEntries); - } - } - - /** Delete specified calls from the call log. */ - private static class DeleteCallsTask extends AsyncTask { - // Use a weak reference to hold the Activity so that there is no memory leak. - private final WeakReference activityWeakReference; - - private final DialerContact contact; - private final CallDetailsEntries callDetailsEntries; - private final String callIds; - - DeleteCallsTask( - Activity activity, DialerContact contact, CallDetailsEntries callDetailsEntries) { - this.activityWeakReference = new WeakReference<>(activity); - this.contact = contact; - this.callDetailsEntries = callDetailsEntries; - - StringBuilder callIds = new StringBuilder(); - for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) { - if (callIds.length() != 0) { - callIds.append(","); - } - callIds.append(entry.getCallId()); - } - this.callIds = callIds.toString(); - } - - @Override - // Suppress the lint check here as the user will not be able to see call log entries if - // permission.WRITE_CALL_LOG is not granted. - @SuppressLint("MissingPermission") - @RequiresPermission(value = permission.WRITE_CALL_LOG) - protected Void doInBackground(Void... params) { - Activity activity = activityWeakReference.get(); - if (activity == null) { - return null; - } - - activity - .getContentResolver() - .delete( - Calls.CONTENT_URI, - CallLog.Calls._ID + " IN (" + callIds + ")" /* where */, - null /* selectionArgs */); - return null; - } - - @Override - public void onPostExecute(Void result) { - Activity activity = activityWeakReference.get(); - if (activity == null) { - return; - } - - Intent data = new Intent(); - data.putExtra(EXTRA_PHONE_NUMBER, contact.getNumber()); - for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) { - if (entry.getHistoryResultsCount() > 0) { - data.putExtra(EXTRA_HAS_ENRICHED_CALL_DATA, true); - break; - } - } - - activity.setResult(RESULT_OK, data); - activity.finish(); - } - } - - private static final class CallDetailsHeaderListener - implements CallDetailsHeaderViewHolder.CallDetailsHeaderListener { - private final WeakReference activityWeakReference; - - CallDetailsHeaderListener(CallDetailsActivity activity) { - this.activityWeakReference = new WeakReference<>(activity); - } - - @Override - public void placeImsVideoCall(String phoneNumber) { - Logger.get(getActivity()) - .logImpression(DialerImpression.Type.CALL_DETAILS_IMS_VIDEO_CALL_BACK); - PreCall.start( - getActivity(), - new CallIntentBuilder(phoneNumber, CallInitiationType.Type.CALL_DETAILS) - .setIsVideoCall(true)); - } - - @Override - public void placeDuoVideoCall(String phoneNumber) { - Logger.get(getActivity()) - .logImpression(DialerImpression.Type.CALL_DETAILS_LIGHTBRINGER_CALL_BACK); - Duo duo = DuoComponent.get(getActivity()).getDuo(); - if (!duo.isReachable(getActivity(), phoneNumber)) { - placeImsVideoCall(phoneNumber); - return; - } - - try { - getActivity() - .startActivityForResult( - duo.getIntent(getActivity(), phoneNumber), ActivityRequestCodes.DIALTACTS_DUO); - } catch (ActivityNotFoundException e) { - Toast.makeText(getActivity(), R.string.activity_not_available, Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void placeVoiceCall(String phoneNumber, String postDialDigits) { - Logger.get(getActivity()).logImpression(DialerImpression.Type.CALL_DETAILS_VOICE_CALL_BACK); - - boolean canSupportedAssistedDialing = - getActivity() - .getIntent() - .getExtras() - .getBoolean(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, false); - CallIntentBuilder callIntentBuilder = - new CallIntentBuilder(phoneNumber + postDialDigits, CallInitiationType.Type.CALL_DETAILS); - if (canSupportedAssistedDialing) { - callIntentBuilder.setAllowAssistedDial(true); - } - - PreCall.start(getActivity(), callIntentBuilder); - } - - private CallDetailsActivity getActivity() { - return Preconditions.checkNotNull(activityWeakReference.get()); - } - - @Override - public void openAssistedDialingSettings(View unused) { - Intent intent = new Intent(getActivity(), AssistedDialingSettingActivity.class); - getActivity().startActivity(intent); - } - - @Override - public void createAssistedDialerNumberParserTask( - AssistedDialingNumberParseWorker worker, - SuccessListener successListener, - FailureListener failureListener) { - DialerExecutorComponent.get(getActivity().getApplicationContext()) - .dialerExecutorFactory() - .createUiTaskBuilder( - getActivity().getFragmentManager(), - "CallDetailsActivity.createAssistedDialerNumberParserTask", - new AssistedDialingNumberParseWorker()) - .onSuccess(successListener) - .onFailure(failureListener) - .build() - .executeParallel(getActivity().contact.getNumber()); - } - } - - static class AssistedDialingNumberParseWorker implements Worker { - - @Override - public Integer doInBackground(@NonNull String phoneNumber) { - PhoneNumber parsedNumber = null; - try { - parsedNumber = PhoneNumberUtil.getInstance().parse(phoneNumber, null); - } catch (NumberParseException e) { - LogUtil.w( - "AssistedDialingNumberParseWorker.doInBackground", - "couldn't parse phone number: " + LogUtil.sanitizePii(phoneNumber), - e); - return 0; - } - return parsedNumber.getCountryCode(); - } - } - - private static final class DeleteCallDetailsListener - implements CallDetailsFooterViewHolder.DeleteCallDetailsListener { - private static final String ASYNC_TASK_ID = "task_delete"; - - private final WeakReference activityWeakReference; - - DeleteCallDetailsListener(CallDetailsActivity activity) { - this.activityWeakReference = new WeakReference<>(activity); - } - - @Override - public void delete() { - AsyncTaskExecutors.createAsyncTaskExecutor() - .submit( - ASYNC_TASK_ID, - new DeleteCallsTask(getActivity(), getActivity().contact, getActivity().entries)); - } - - private CallDetailsActivity getActivity() { - return Preconditions.checkNotNull(activityWeakReference.get()); - } - } - - private static final class ReportCallIdListener - implements CallDetailsFooterViewHolder.ReportCallIdListener { - private final WeakReference activityWeakReference; - - ReportCallIdListener(Activity activity) { - this.activityWeakReference = new WeakReference<>(activity); - } - - @Override - public void reportCallId(String number) { - ReportDialogFragment.newInstance(number) - .show(getActivity().getFragmentManager(), null /* tag */); - } - - @Override - public boolean canReportCallerId(String number) { - return getActivity().getIntent().getExtras().getBoolean(EXTRA_CAN_REPORT_CALLER_ID, false); - } - - private Activity getActivity() { - return Preconditions.checkNotNull(activityWeakReference.get()); - } - } - - private static final class EnrichedCallHistoricalDataChangedListener - implements EnrichedCallManager.HistoricalDataChangedListener { - private final WeakReference activityWeakReference; - - EnrichedCallHistoricalDataChangedListener(CallDetailsActivity activity) { - this.activityWeakReference = new WeakReference<>(activity); - } - - @Override - public void onHistoricalDataChanged() { - CallDetailsActivity activity = getActivity(); - Map> mappedResults = - getAllHistoricalData(activity.contact.getNumber(), activity.entries); - - activity.adapter.updateCallDetailsEntries( - generateAndMapNewCallDetailsEntriesHistoryResults( - activity.contact.getNumber(), activity.entries, mappedResults) - .getEntriesList()); - } - - private CallDetailsActivity getActivity() { - return Preconditions.checkNotNull(activityWeakReference.get()); - } - - @NonNull - private Map> getAllHistoricalData( - @Nullable String number, @NonNull CallDetailsEntries entries) { - if (number == null) { - return Collections.emptyMap(); - } - - Map> historicalData = - EnrichedCallComponent.get(getActivity()) - .getEnrichedCallManager() - .getAllHistoricalData(number, entries); - if (historicalData == null) { - return Collections.emptyMap(); - } - return historicalData; - } - - private static CallDetailsEntries generateAndMapNewCallDetailsEntriesHistoryResults( - @Nullable String number, - @NonNull CallDetailsEntries callDetailsEntries, - @NonNull Map> mappedResults) { - if (number == null) { - return callDetailsEntries; - } - CallDetailsEntries.Builder mutableCallDetailsEntries = CallDetailsEntries.newBuilder(); - for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) { - CallDetailsEntry.Builder newEntry = CallDetailsEntry.newBuilder().mergeFrom(entry); - List results = mappedResults.get(entry); - if (results != null) { - newEntry.addAllHistoryResults(mappedResults.get(entry)); - LogUtil.v( - "CallDetailsActivity.generateAndMapNewCallDetailsEntriesHistoryResults", - "mapped %d results", - newEntry.getHistoryResultsList().size()); - } - mutableCallDetailsEntries.addEntries(newEntry.build()); - } - return mutableCallDetailsEntries.build(); + .requestAllHistoricalData(activity.getNumber(), newEntries); } } } diff --git a/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java b/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java new file mode 100644 index 000000000..45200b9ce --- /dev/null +++ b/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java @@ -0,0 +1,466 @@ +/* + * 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.calldetails; + +import android.Manifest.permission; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.provider.CallLog; +import android.provider.CallLog.Calls; +import android.support.annotation.CallSuper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresPermission; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.widget.Toast; +import com.android.dialer.assisteddialing.ui.AssistedDialingSettingActivity; +import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.callintent.CallInitiationType; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.FailureListener; +import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import com.android.dialer.common.concurrent.DialerExecutorComponent; +import com.android.dialer.common.database.Selection; +import com.android.dialer.constants.ActivityRequestCodes; +import com.android.dialer.duo.Duo; +import com.android.dialer.duo.DuoComponent; +import com.android.dialer.enrichedcall.EnrichedCallComponent; +import com.android.dialer.enrichedcall.EnrichedCallManager; +import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.UiAction; +import com.android.dialer.performancereport.PerformanceReport; +import com.android.dialer.postcall.PostCall; +import com.android.dialer.precall.PreCall; +import com.google.common.base.Preconditions; +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Contains common logic shared between {@link OldCallDetailsActivity} and {@link + * CallDetailsActivity}. + */ +abstract class CallDetailsActivityCommon extends AppCompatActivity { + + public static final String EXTRA_PHONE_NUMBER = "phone_number"; + public static final String EXTRA_HAS_ENRICHED_CALL_DATA = "has_enriched_call_data"; + public static final String EXTRA_CAN_REPORT_CALLER_ID = "can_report_caller_id"; + public static final String EXTRA_CAN_SUPPORT_ASSISTED_DIALING = "can_support_assisted_dialing"; + + private final CallDetailsHeaderViewHolder.CallDetailsHeaderListener callDetailsHeaderListener = + new CallDetailsHeaderListener(this); + private final CallDetailsFooterViewHolder.DeleteCallDetailsListener deleteCallDetailsListener = + new DeleteCallDetailsListener(this); + private final CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener = + new ReportCallIdListener(this); + private final EnrichedCallManager.HistoricalDataChangedListener + enrichedCallHistoricalDataChangedListener = + new EnrichedCallHistoricalDataChangedListener(this); + + private CallDetailsAdapterCommon adapter; + private CallDetailsEntries callDetailsEntries; + + /** + * Handles the intent that launches {@link OldCallDetailsActivity} or {@link CallDetailsActivity}, + * e.g., extract data from intent extras, start loading data, etc. + */ + protected abstract void handleIntent(Intent intent); + + /** Creates an adapter for {@link OldCallDetailsActivity} or {@link CallDetailsActivity}. */ + protected abstract CallDetailsAdapterCommon createAdapter( + CallDetailsHeaderViewHolder.CallDetailsHeaderListener callDetailsHeaderListener, + CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener, + CallDetailsFooterViewHolder.DeleteCallDetailsListener deleteCallDetailsListener); + + /** Returns the phone number of the call details. */ + protected abstract String getNumber(); + + @Override + @CallSuper + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.call_details_activity); + Toolbar toolbar = findViewById(R.id.toolbar); + toolbar.setTitle(R.string.call_details); + toolbar.setNavigationOnClickListener( + v -> { + PerformanceReport.recordClick(UiAction.Type.CLOSE_CALL_DETAIL_WITH_CANCEL_BUTTON); + finish(); + }); + handleIntent(getIntent()); + setupRecyclerViewForEntries(); + } + + @Override + @CallSuper + protected void onResume() { + super.onResume(); + + // Some calls may not be recorded (eg. from quick contact), + // so we should restart recording after these calls. (Recorded call is stopped) + PostCall.restartPerformanceRecordingIfARecentCallExist(this); + if (!PerformanceReport.isRecording()) { + PerformanceReport.startRecording(); + } + + PostCall.promptUserForMessageIfNecessary(this, findViewById(R.id.recycler_view)); + + EnrichedCallComponent.get(this) + .getEnrichedCallManager() + .registerHistoricalDataChangedListener(enrichedCallHistoricalDataChangedListener); + EnrichedCallComponent.get(this) + .getEnrichedCallManager() + .requestAllHistoricalData(getNumber(), callDetailsEntries); + } + + @Override + @CallSuper + protected void onPause() { + super.onPause(); + + EnrichedCallComponent.get(this) + .getEnrichedCallManager() + .unregisterHistoricalDataChangedListener(enrichedCallHistoricalDataChangedListener); + } + + @Override + @CallSuper + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + handleIntent(intent); + setupRecyclerViewForEntries(); + } + + private void setupRecyclerViewForEntries() { + adapter = + createAdapter(callDetailsHeaderListener, reportCallIdListener, deleteCallDetailsListener); + + RecyclerView recyclerView = findViewById(R.id.recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + recyclerView.setAdapter(adapter); + PerformanceReport.logOnScrollStateChange(recyclerView); + } + + final CallDetailsAdapterCommon getAdapter() { + return adapter; + } + + @Override + @CallSuper + public void onBackPressed() { + PerformanceReport.recordClick(UiAction.Type.PRESS_ANDROID_BACK_BUTTON); + super.onBackPressed(); + } + + protected final void setCallDetailsEntries(CallDetailsEntries entries) { + this.callDetailsEntries = entries; + } + + protected final CallDetailsEntries getCallDetailsEntries() { + return callDetailsEntries; + } + + /** A {@link Worker} that deletes specified entries from the call log. */ + private static final class DeleteCallsWorker implements Worker { + // Use a weak reference to hold the Activity so that there is no memory leak. + private final WeakReference contextWeakReference; + + DeleteCallsWorker(Context context) { + this.contextWeakReference = new WeakReference<>(context); + } + + @Override + // Suppress the lint check here as the user will not be able to see call log entries if + // permission.WRITE_CALL_LOG is not granted. + @SuppressLint("MissingPermission") + @RequiresPermission(value = permission.WRITE_CALL_LOG) + public Void doInBackground(CallDetailsEntries callDetailsEntries) { + Context context = contextWeakReference.get(); + if (context == null) { + return null; + } + + Selection selection = + Selection.builder() + .and(Selection.column(CallLog.Calls._ID).in(getCallLogIdList(callDetailsEntries))) + .build(); + + context + .getContentResolver() + .delete(Calls.CONTENT_URI, selection.getSelection(), selection.getSelectionArgs()); + return null; + } + + private static List getCallLogIdList(CallDetailsEntries callDetailsEntries) { + Assert.checkArgument(callDetailsEntries.getEntriesCount() > 0); + + List idStrings = new ArrayList<>(callDetailsEntries.getEntriesCount()); + + for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) { + idStrings.add(String.valueOf(entry.getCallId())); + } + + return idStrings; + } + } + + private static final class CallDetailsHeaderListener + implements CallDetailsHeaderViewHolder.CallDetailsHeaderListener { + private final WeakReference activityWeakReference; + + CallDetailsHeaderListener(CallDetailsActivityCommon activity) { + this.activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void placeImsVideoCall(String phoneNumber) { + Logger.get(getActivity()) + .logImpression(DialerImpression.Type.CALL_DETAILS_IMS_VIDEO_CALL_BACK); + PreCall.start( + getActivity(), + new CallIntentBuilder(phoneNumber, CallInitiationType.Type.CALL_DETAILS) + .setIsVideoCall(true)); + } + + @Override + public void placeDuoVideoCall(String phoneNumber) { + Logger.get(getActivity()) + .logImpression(DialerImpression.Type.CALL_DETAILS_LIGHTBRINGER_CALL_BACK); + Duo duo = DuoComponent.get(getActivity()).getDuo(); + if (!duo.isReachable(getActivity(), phoneNumber)) { + placeImsVideoCall(phoneNumber); + return; + } + + try { + getActivity() + .startActivityForResult( + duo.getIntent(getActivity(), phoneNumber), ActivityRequestCodes.DIALTACTS_DUO); + } catch (ActivityNotFoundException e) { + Toast.makeText(getActivity(), R.string.activity_not_available, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void placeVoiceCall(String phoneNumber, String postDialDigits) { + Logger.get(getActivity()).logImpression(DialerImpression.Type.CALL_DETAILS_VOICE_CALL_BACK); + + boolean canSupportedAssistedDialing = + getActivity() + .getIntent() + .getExtras() + .getBoolean(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, false); + CallIntentBuilder callIntentBuilder = + new CallIntentBuilder(phoneNumber + postDialDigits, CallInitiationType.Type.CALL_DETAILS); + if (canSupportedAssistedDialing) { + callIntentBuilder.setAllowAssistedDial(true); + } + + PreCall.start(getActivity(), callIntentBuilder); + } + + private CallDetailsActivityCommon getActivity() { + return Preconditions.checkNotNull(activityWeakReference.get()); + } + + @Override + public void openAssistedDialingSettings(View unused) { + Intent intent = new Intent(getActivity(), AssistedDialingSettingActivity.class); + getActivity().startActivity(intent); + } + + @Override + public void createAssistedDialerNumberParserTask( + AssistedDialingNumberParseWorker worker, + SuccessListener successListener, + FailureListener failureListener) { + DialerExecutorComponent.get(getActivity().getApplicationContext()) + .dialerExecutorFactory() + .createUiTaskBuilder( + getActivity().getFragmentManager(), + "CallDetailsActivityCommon.createAssistedDialerNumberParserTask", + new AssistedDialingNumberParseWorker()) + .onSuccess(successListener) + .onFailure(failureListener) + .build() + .executeParallel(getActivity().getNumber()); + } + } + + static final class AssistedDialingNumberParseWorker implements Worker { + + @Override + public Integer doInBackground(@NonNull String phoneNumber) { + PhoneNumber parsedNumber; + try { + parsedNumber = PhoneNumberUtil.getInstance().parse(phoneNumber, null); + } catch (NumberParseException e) { + LogUtil.w( + "AssistedDialingNumberParseWorker.doInBackground", + "couldn't parse phone number: " + LogUtil.sanitizePii(phoneNumber), + e); + return 0; + } + return parsedNumber.getCountryCode(); + } + } + + private static final class DeleteCallDetailsListener + implements CallDetailsFooterViewHolder.DeleteCallDetailsListener { + + private final WeakReference activityWeakReference; + + DeleteCallDetailsListener(CallDetailsActivityCommon activity) { + this.activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void delete() { + CallDetailsActivityCommon activity = getActivity(); + DialerExecutorComponent.get(activity) + .dialerExecutorFactory() + .createNonUiTaskBuilder(new DeleteCallsWorker(activity)) + .onSuccess( + unused -> { + Intent data = new Intent(); + data.putExtra(EXTRA_PHONE_NUMBER, activity.getNumber()); + for (CallDetailsEntry entry : activity.getCallDetailsEntries().getEntriesList()) { + if (entry.getHistoryResultsCount() > 0) { + data.putExtra(EXTRA_HAS_ENRICHED_CALL_DATA, true); + break; + } + } + + activity.setResult(RESULT_OK, data); + activity.finish(); + }) + .build() + .executeSerial(activity.getCallDetailsEntries()); + } + + private CallDetailsActivityCommon getActivity() { + return Preconditions.checkNotNull(activityWeakReference.get()); + } + } + + private static final class ReportCallIdListener + implements CallDetailsFooterViewHolder.ReportCallIdListener { + private final WeakReference activityWeakReference; + + ReportCallIdListener(Activity activity) { + this.activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void reportCallId(String number) { + ReportDialogFragment.newInstance(number) + .show(getActivity().getFragmentManager(), null /* tag */); + } + + @Override + public boolean canReportCallerId(String number) { + return getActivity().getIntent().getExtras().getBoolean(EXTRA_CAN_REPORT_CALLER_ID, false); + } + + private Activity getActivity() { + return Preconditions.checkNotNull(activityWeakReference.get()); + } + } + + private static final class EnrichedCallHistoricalDataChangedListener + implements EnrichedCallManager.HistoricalDataChangedListener { + private final WeakReference activityWeakReference; + + EnrichedCallHistoricalDataChangedListener(CallDetailsActivityCommon activity) { + this.activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void onHistoricalDataChanged() { + CallDetailsActivityCommon activity = getActivity(); + Map> mappedResults = + getAllHistoricalData(activity.getNumber(), activity.callDetailsEntries); + + activity.adapter.updateCallDetailsEntries( + generateAndMapNewCallDetailsEntriesHistoryResults( + activity.getNumber(), activity.callDetailsEntries, mappedResults)); + } + + private CallDetailsActivityCommon getActivity() { + return Preconditions.checkNotNull(activityWeakReference.get()); + } + + @NonNull + private Map> getAllHistoricalData( + @Nullable String number, @NonNull CallDetailsEntries entries) { + if (number == null) { + return Collections.emptyMap(); + } + + Map> historicalData = + EnrichedCallComponent.get(getActivity()) + .getEnrichedCallManager() + .getAllHistoricalData(number, entries); + if (historicalData == null) { + return Collections.emptyMap(); + } + return historicalData; + } + + private static CallDetailsEntries generateAndMapNewCallDetailsEntriesHistoryResults( + @Nullable String number, + @NonNull CallDetailsEntries callDetailsEntries, + @NonNull Map> mappedResults) { + if (number == null) { + return callDetailsEntries; + } + CallDetailsEntries.Builder mutableCallDetailsEntries = CallDetailsEntries.newBuilder(); + for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) { + CallDetailsEntry.Builder newEntry = CallDetailsEntry.newBuilder().mergeFrom(entry); + List results = mappedResults.get(entry); + if (results != null) { + newEntry.addAllHistoryResults(mappedResults.get(entry)); + LogUtil.v( + "CallDetailsActivityCommon.generateAndMapNewCallDetailsEntriesHistoryResults", + "mapped %d results", + newEntry.getHistoryResultsList().size()); + } + mutableCallDetailsEntries.addEntries(newEntry.build()); + } + return mutableCallDetailsEntries.build(); + } + } +} diff --git a/java/com/android/dialer/calldetails/CallDetailsAdapter.java b/java/com/android/dialer/calldetails/CallDetailsAdapter.java index 030366e9f..dfa472a71 100644 --- a/java/com/android/dialer/calldetails/CallDetailsAdapter.java +++ b/java/com/android/dialer/calldetails/CallDetailsAdapter.java @@ -17,119 +17,55 @@ package com.android.dialer.calldetails; import android.content.Context; -import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.ViewHolder; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; +import android.view.View; import com.android.dialer.calldetails.CallDetailsFooterViewHolder.DeleteCallDetailsListener; import com.android.dialer.calldetails.CallDetailsHeaderViewHolder.CallDetailsHeaderListener; -import com.android.dialer.calllogutils.CallTypeHelper; -import com.android.dialer.calllogutils.CallbackActionHelper; -import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction; -import com.android.dialer.common.Assert; import com.android.dialer.dialercontact.DialerContact; -import com.android.dialer.duo.DuoComponent; -import java.util.List; -/** Adapter for RecyclerView in {@link CallDetailsActivity}. */ -final class CallDetailsAdapter extends RecyclerView.Adapter { - - private static final int HEADER_VIEW_TYPE = 1; - private static final int CALL_ENTRY_VIEW_TYPE = 2; - private static final int FOOTER_VIEW_TYPE = 3; +/** + * A {@link RecyclerView.Adapter} for {@link CallDetailsActivity}. + * + *

See {@link CallDetailsAdapterCommon} for logic shared between this adapter and {@link + * OldCallDetailsAdapter}. + */ +final class CallDetailsAdapter extends CallDetailsAdapterCommon { private final DialerContact contact; - private final CallDetailsHeaderListener callDetailsHeaderListener; - private final CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener; - private final DeleteCallDetailsListener deleteCallDetailsListener; - private final CallTypeHelper callTypeHelper; - private List callDetailsEntries; CallDetailsAdapter( Context context, - @NonNull DialerContact contact, - @NonNull List callDetailsEntries, + DialerContact contact, + CallDetailsEntries callDetailsEntries, CallDetailsHeaderListener callDetailsHeaderListener, CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener, DeleteCallDetailsListener deleteCallDetailsListener) { - this.contact = Assert.isNotNull(contact); - this.callDetailsEntries = callDetailsEntries; - this.callDetailsHeaderListener = callDetailsHeaderListener; - this.reportCallIdListener = reportCallIdListener; - this.deleteCallDetailsListener = deleteCallDetailsListener; - callTypeHelper = new CallTypeHelper(context.getResources(), DuoComponent.get(context).getDuo()); - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - switch (viewType) { - case HEADER_VIEW_TYPE: - return new CallDetailsHeaderViewHolder( - inflater.inflate(R.layout.contact_container, parent, false), callDetailsHeaderListener); - case CALL_ENTRY_VIEW_TYPE: - return new CallDetailsEntryViewHolder( - inflater.inflate(R.layout.call_details_entry, parent, false)); - case FOOTER_VIEW_TYPE: - return new CallDetailsFooterViewHolder( - inflater.inflate(R.layout.call_details_footer, parent, false), - reportCallIdListener, - deleteCallDetailsListener); - default: - throw Assert.createIllegalStateFailException( - "No ViewHolder available for viewType: " + viewType); - } + super( + context, + callDetailsEntries, + callDetailsHeaderListener, + reportCallIdListener, + deleteCallDetailsListener); + this.contact = contact; } @Override - public void onBindViewHolder(ViewHolder holder, int position) { - if (position == 0) { // Header - ((CallDetailsHeaderViewHolder) holder).updateContactInfo(contact, getCallbackAction()); - ((CallDetailsHeaderViewHolder) holder) - .updateAssistedDialingInfo(callDetailsEntries.get(position)); - } else if (position == getItemCount() - 1) { - ((CallDetailsFooterViewHolder) holder).setPhoneNumber(contact.getNumber()); - } else { - CallDetailsEntryViewHolder viewHolder = (CallDetailsEntryViewHolder) holder; - CallDetailsEntry entry = callDetailsEntries.get(position - 1); - viewHolder.setCallDetails( - contact.getNumber(), - entry, - callTypeHelper, - !entry.getHistoryResultsList().isEmpty() && position != getItemCount() - 2); - } + protected CallDetailsHeaderViewHolder createCallDetailsHeaderViewHolder( + View container, CallDetailsHeaderListener callDetailsHeaderListener) { + return new CallDetailsHeaderViewHolder( + container, contact.getNumber(), contact.getPostDialDigits(), callDetailsHeaderListener); } @Override - public int getItemViewType(int position) { - if (position == 0) { // Header - return HEADER_VIEW_TYPE; - } else if (position == getItemCount() - 1) { - return FOOTER_VIEW_TYPE; - } else { - return CALL_ENTRY_VIEW_TYPE; - } + protected void bindCallDetailsHeaderViewHolder( + CallDetailsHeaderViewHolder callDetailsHeaderViewHolder, int position) { + callDetailsHeaderViewHolder.updateContactInfo(contact, getCallbackAction()); + callDetailsHeaderViewHolder.updateAssistedDialingInfo( + getCallDetailsEntries().getEntries(position)); } @Override - public int getItemCount() { - return callDetailsEntries.isEmpty() - ? 0 - : callDetailsEntries.size() + 2; // plus header and footer - } - - void updateCallDetailsEntries(List entries) { - callDetailsEntries = entries; - notifyDataSetChanged(); - } - - private @CallbackAction int getCallbackAction() { - Assert.checkState(!callDetailsEntries.isEmpty()); - - CallDetailsEntry entry = callDetailsEntries.get(0); - return CallbackActionHelper.getCallbackAction( - contact.getNumber(), entry.getFeatures(), entry.getIsDuoCall()); + protected String getNumber() { + return contact.getNumber(); } } diff --git a/java/com/android/dialer/calldetails/CallDetailsAdapterCommon.java b/java/com/android/dialer/calldetails/CallDetailsAdapterCommon.java new file mode 100644 index 000000000..27feff89d --- /dev/null +++ b/java/com/android/dialer/calldetails/CallDetailsAdapterCommon.java @@ -0,0 +1,152 @@ +/* + * 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.calldetails; + +import android.content.Context; +import android.support.annotation.CallSuper; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.ViewHolder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.calldetails.CallDetailsFooterViewHolder.DeleteCallDetailsListener; +import com.android.dialer.calldetails.CallDetailsFooterViewHolder.ReportCallIdListener; +import com.android.dialer.calldetails.CallDetailsHeaderViewHolder.CallDetailsHeaderListener; +import com.android.dialer.calllogutils.CallTypeHelper; +import com.android.dialer.calllogutils.CallbackActionHelper; +import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction; +import com.android.dialer.common.Assert; +import com.android.dialer.duo.DuoComponent; + +/** + * Contains common logic shared between {@link OldCallDetailsAdapter} and {@link + * CallDetailsAdapter}. + */ +abstract class CallDetailsAdapterCommon extends RecyclerView.Adapter { + + private static final int HEADER_VIEW_TYPE = 1; + private static final int CALL_ENTRY_VIEW_TYPE = 2; + private static final int FOOTER_VIEW_TYPE = 3; + + private final CallDetailsHeaderListener callDetailsHeaderListener; + private final ReportCallIdListener reportCallIdListener; + private final DeleteCallDetailsListener deleteCallDetailsListener; + private final CallTypeHelper callTypeHelper; + + private CallDetailsEntries callDetailsEntries; + + protected abstract void bindCallDetailsHeaderViewHolder( + CallDetailsHeaderViewHolder viewHolder, int position); + + protected abstract CallDetailsHeaderViewHolder createCallDetailsHeaderViewHolder( + View container, CallDetailsHeaderListener callDetailsHeaderListener); + + /** Returns the phone number of the call details. */ + protected abstract String getNumber(); + + CallDetailsAdapterCommon( + Context context, + CallDetailsEntries callDetailsEntries, + CallDetailsHeaderListener callDetailsHeaderListener, + ReportCallIdListener reportCallIdListener, + DeleteCallDetailsListener deleteCallDetailsListener) { + this.callDetailsEntries = callDetailsEntries; + this.callDetailsHeaderListener = callDetailsHeaderListener; + this.reportCallIdListener = reportCallIdListener; + this.deleteCallDetailsListener = deleteCallDetailsListener; + this.callTypeHelper = + new CallTypeHelper(context.getResources(), DuoComponent.get(context).getDuo()); + } + + @Override + @CallSuper + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + switch (viewType) { + case HEADER_VIEW_TYPE: + return createCallDetailsHeaderViewHolder( + inflater.inflate(R.layout.contact_container, parent, false), callDetailsHeaderListener); + case CALL_ENTRY_VIEW_TYPE: + return new CallDetailsEntryViewHolder( + inflater.inflate(R.layout.call_details_entry, parent, false)); + case FOOTER_VIEW_TYPE: + return new CallDetailsFooterViewHolder( + inflater.inflate(R.layout.call_details_footer, parent, false), + reportCallIdListener, + deleteCallDetailsListener); + default: + throw Assert.createIllegalStateFailException( + "No ViewHolder available for viewType: " + viewType); + } + } + + @Override + @CallSuper + public void onBindViewHolder(ViewHolder holder, int position) { + if (position == 0) { // Header + bindCallDetailsHeaderViewHolder((CallDetailsHeaderViewHolder) holder, position); + } else if (position == getItemCount() - 1) { + ((CallDetailsFooterViewHolder) holder).setPhoneNumber(getNumber()); + } else { + CallDetailsEntryViewHolder viewHolder = (CallDetailsEntryViewHolder) holder; + CallDetailsEntry entry = callDetailsEntries.getEntries(position - 1); + viewHolder.setCallDetails( + getNumber(), + entry, + callTypeHelper, + !entry.getHistoryResultsList().isEmpty() && position != getItemCount() - 2); + } + } + + @Override + @CallSuper + public int getItemViewType(int position) { + if (position == 0) { // Header + return HEADER_VIEW_TYPE; + } else if (position == getItemCount() - 1) { + return FOOTER_VIEW_TYPE; + } else { + return CALL_ENTRY_VIEW_TYPE; + } + } + + @Override + @CallSuper + public int getItemCount() { + return callDetailsEntries.getEntriesCount() == 0 + ? 0 + : callDetailsEntries.getEntriesCount() + 2; // plus header and footer + } + + final CallDetailsEntries getCallDetailsEntries() { + return callDetailsEntries; + } + + final void updateCallDetailsEntries(CallDetailsEntries entries) { + callDetailsEntries = entries; + notifyDataSetChanged(); + } + + final @CallbackAction int getCallbackAction() { + Assert.checkState(!callDetailsEntries.getEntriesList().isEmpty()); + + CallDetailsEntry entry = callDetailsEntries.getEntries(0); + return CallbackActionHelper.getCallbackAction( + getNumber(), entry.getFeatures(), entry.getIsDuoCall()); + } +} diff --git a/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java b/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java index 15c64c927..3b8af4026 100644 --- a/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java +++ b/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java @@ -73,8 +73,8 @@ public final class CallDetailsCursorLoader extends CursorLoader { // the data loading but no data can be fetched and we want to ensure the data set is not empty // when building CallDetailsEntries proto (see toCallDetailsEntries(Cursor)). // - // CallDetailsActivity doesn't respond to underlying data changes when launched from the old - // call log and we decided to keep it that way when launched from the new call log. + // OldCallDetailsActivity doesn't respond to underlying data changes and we decided to keep it + // that way in CallDetailsActivity. } /** diff --git a/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java index c65bb9196..f50876d50 100644 --- a/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java +++ b/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java @@ -40,7 +40,7 @@ import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil; -/** ViewHolder for call entries in {@link CallDetailsActivity}. */ +/** ViewHolder for call entries in {@link OldCallDetailsActivity} or {@link CallDetailsActivity}. */ public class CallDetailsEntryViewHolder extends ViewHolder { private final CallTypeIconsView callTypeIcon; diff --git a/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java index 30b28d83a..d6e6dbec6 100644 --- a/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java +++ b/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java @@ -31,7 +31,7 @@ import com.android.dialer.performancereport.PerformanceReport; import com.android.dialer.util.CallUtil; import com.android.dialer.util.DialerUtils; -/** ViewHolder container for {@link CallDetailsActivity} footer. */ +/** ViewHolder for the footer in {@link OldCallDetailsActivity} or {@link CallDetailsActivity}. */ final class CallDetailsFooterViewHolder extends RecyclerView.ViewHolder implements OnClickListener { private final ReportCallIdListener reportCallIdListener; @@ -91,7 +91,8 @@ final class CallDetailsFooterViewHolder extends RecyclerView.ViewHolder implemen } else if (view == delete) { deleteCallDetailsListener.delete(); } else { - Assert.fail("View on click not implemented: " + view); + throw Assert.createUnsupportedOperationFailException( + "View on click not implemented: " + view); } } diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java index 647c6edb6..34ed68836 100644 --- a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java +++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java @@ -27,7 +27,7 @@ import android.widget.ImageView; import android.widget.QuickContactBadge; import android.widget.RelativeLayout; import android.widget.TextView; -import com.android.dialer.calldetails.CallDetailsActivity.AssistedDialingNumberParseWorker; +import com.android.dialer.calldetails.CallDetailsActivityCommon.AssistedDialingNumberParseWorker; import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction; import com.android.dialer.common.Assert; @@ -40,7 +40,11 @@ import com.android.dialer.dialercontact.DialerContact; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; -/** ViewHolder for Header/Contact in {@link CallDetailsActivity}. */ +/** + * ViewHolder for the header in {@link OldCallDetailsActivity} or {@link CallDetailsActivity}. + * + *

The header contains contact info and the primary callback button. + */ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder implements OnClickListener, FailureListener { @@ -54,10 +58,16 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder private final TextView assistedDialingInternationalDirectDialCodeAndCountryCodeText; private final RelativeLayout assistedDialingContainer; - private DialerContact contact; + private final String number; + private final String postDialDigits; + private @CallbackAction int callbackAction; - CallDetailsHeaderViewHolder(View container, CallDetailsHeaderListener callDetailsHeaderListener) { + CallDetailsHeaderViewHolder( + View container, + String number, + String postDialDigits, + CallDetailsHeaderListener callDetailsHeaderListener) { super(container); context = container.getContext(); callbackButton = container.findViewById(R.id.call_back_button); @@ -73,7 +83,11 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder callDetailsHeaderListener::openAssistedDialingSettings); callbackButton.setOnClickListener(this); + + this.number = number; + this.postDialDigits = postDialDigits; this.callDetailsHeaderListener = callDetailsHeaderListener; + Logger.get(context) .logQuickContactOnTouch( contactPhoto, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CALL_DETAILS, true); @@ -89,7 +103,7 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder if (callDetailsEntry != null && hasAssistedDialingFeature(callDetailsEntry.getFeatures())) { showAssistedDialingContainer(true); callDetailsHeaderListener.createAssistedDialerNumberParserTask( - new CallDetailsActivity.AssistedDialingNumberParseWorker(), + new CallDetailsActivityCommon.AssistedDialingNumberParseWorker(), this::updateAssistedDialingText, this::onFailure); @@ -132,7 +146,6 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder /** Populates the contact info fields based on the current contact information. */ void updateContactInfo(DialerContact contact, @CallbackAction int callbackAction) { - this.contact = contact; ContactPhotoManager.getInstance(context) .loadDialerThumbnailOrPhoto( contactPhoto, @@ -194,14 +207,13 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder if (view == callbackButton) { switch (callbackAction) { case CallbackAction.IMS_VIDEO: - callDetailsHeaderListener.placeImsVideoCall(contact.getNumber()); + callDetailsHeaderListener.placeImsVideoCall(number); break; case CallbackAction.DUO: - callDetailsHeaderListener.placeDuoVideoCall(contact.getNumber()); + callDetailsHeaderListener.placeDuoVideoCall(number); break; case CallbackAction.VOICE: - callDetailsHeaderListener.placeVoiceCall( - contact.getNumber(), contact.getPostDialDigits()); + callDetailsHeaderListener.placeVoiceCall(number, postDialDigits); break; case CallbackAction.NONE: default: diff --git a/java/com/android/dialer/calldetails/OldCallDetailsActivity.java b/java/com/android/dialer/calldetails/OldCallDetailsActivity.java new file mode 100644 index 000000000..1891265fb --- /dev/null +++ b/java/com/android/dialer/calldetails/OldCallDetailsActivity.java @@ -0,0 +1,94 @@ +/* + * 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.calldetails; + +import android.content.Context; +import android.content.Intent; +import com.android.dialer.calldetails.CallDetailsFooterViewHolder.DeleteCallDetailsListener; +import com.android.dialer.calldetails.CallDetailsFooterViewHolder.ReportCallIdListener; +import com.android.dialer.calldetails.CallDetailsHeaderViewHolder.CallDetailsHeaderListener; +import com.android.dialer.common.Assert; +import com.android.dialer.dialercontact.DialerContact; +import com.android.dialer.protos.ProtoParsers; + +/** + * Displays the details of a specific call log entry. + * + *

This activity is for the old call log. + * + *

See {@link CallDetailsAdapterCommon} for logic shared between this activity and the one for + * the new call log. + */ +public final class OldCallDetailsActivity extends CallDetailsActivityCommon { + public static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries"; + public static final String EXTRA_CONTACT = "contact"; + + /** Contains info to be shown in the header. */ + private DialerContact contact; + + public static boolean isLaunchIntent(Intent intent) { + return intent.getComponent() != null + && OldCallDetailsActivity.class.getName().equals(intent.getComponent().getClassName()); + } + + /** Returns an {@link Intent} to launch this activity. */ + public static Intent newInstance( + Context context, + CallDetailsEntries details, + DialerContact contact, + boolean canReportCallerId, + boolean canSupportAssistedDialing) { + Intent intent = new Intent(context, OldCallDetailsActivity.class); + ProtoParsers.put(intent, EXTRA_CONTACT, Assert.isNotNull(contact)); + ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, Assert.isNotNull(details)); + intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId); + intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing); + return intent; + } + + @Override + protected void handleIntent(Intent intent) { + Assert.checkArgument(intent.hasExtra(EXTRA_CONTACT)); + Assert.checkArgument(intent.hasExtra(EXTRA_CALL_DETAILS_ENTRIES)); + Assert.checkArgument(intent.hasExtra(EXTRA_CAN_REPORT_CALLER_ID)); + Assert.checkArgument(intent.hasExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING)); + + contact = ProtoParsers.getTrusted(intent, EXTRA_CONTACT, DialerContact.getDefaultInstance()); + setCallDetailsEntries( + ProtoParsers.getTrusted( + intent, EXTRA_CALL_DETAILS_ENTRIES, CallDetailsEntries.getDefaultInstance())); + } + + @Override + protected CallDetailsAdapterCommon createAdapter( + CallDetailsHeaderListener callDetailsHeaderListener, + ReportCallIdListener reportCallIdListener, + DeleteCallDetailsListener deleteCallDetailsListener) { + return new OldCallDetailsAdapter( + /* context = */ this, + contact, + getCallDetailsEntries(), + callDetailsHeaderListener, + reportCallIdListener, + deleteCallDetailsListener); + } + + @Override + protected String getNumber() { + return contact.getNumber(); + } +} diff --git a/java/com/android/dialer/calldetails/OldCallDetailsAdapter.java b/java/com/android/dialer/calldetails/OldCallDetailsAdapter.java new file mode 100644 index 000000000..010f5cd6c --- /dev/null +++ b/java/com/android/dialer/calldetails/OldCallDetailsAdapter.java @@ -0,0 +1,73 @@ +/* + * 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.calldetails; + + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import com.android.dialer.calldetails.CallDetailsFooterViewHolder.DeleteCallDetailsListener; +import com.android.dialer.calldetails.CallDetailsHeaderViewHolder.CallDetailsHeaderListener; +import com.android.dialer.dialercontact.DialerContact; + +/** + * A {@link RecyclerView.Adapter} for {@link OldCallDetailsActivity}. + * + *

See {@link CallDetailsAdapterCommon} for logic shared between this adapter and {@link + * CallDetailsAdapter}. + */ +final class OldCallDetailsAdapter extends CallDetailsAdapterCommon { + + /** Contains info to be shown in the header. */ + private final DialerContact contact; + + OldCallDetailsAdapter( + Context context, + DialerContact contact, + CallDetailsEntries callDetailsEntries, + CallDetailsHeaderListener callDetailsHeaderListener, + CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener, + DeleteCallDetailsListener deleteCallDetailsListener) { + super( + context, + callDetailsEntries, + callDetailsHeaderListener, + reportCallIdListener, + deleteCallDetailsListener); + this.contact = contact; + } + + @Override + protected CallDetailsHeaderViewHolder createCallDetailsHeaderViewHolder( + View container, CallDetailsHeaderListener callDetailsHeaderListener) { + return new CallDetailsHeaderViewHolder( + container, contact.getNumber(), contact.getPostDialDigits(), callDetailsHeaderListener); + } + + @Override + protected void bindCallDetailsHeaderViewHolder( + CallDetailsHeaderViewHolder callDetailsHeaderViewHolder, int position) { + callDetailsHeaderViewHolder.updateContactInfo(contact, getCallbackAction()); + callDetailsHeaderViewHolder.updateAssistedDialingInfo( + getCallDetailsEntries().getEntries(position)); + } + + @Override + protected String getNumber() { + return contact.getNumber(); + } +} diff --git a/java/com/android/dialer/calldetails/ReportDialogFragment.java b/java/com/android/dialer/calldetails/ReportDialogFragment.java index 0861c9dbb..d75fe51e2 100644 --- a/java/com/android/dialer/calldetails/ReportDialogFragment.java +++ b/java/com/android/dialer/calldetails/ReportDialogFragment.java @@ -38,7 +38,7 @@ import com.android.dialer.phonenumbercache.CachedNumberLookupService; import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo; import com.android.dialer.phonenumbercache.PhoneNumberCache; -/** Dialog for reporting an inaccurate caller id information in {@link CallDetailsActivity}. */ +/** Dialog for reporting an inaccurate caller id information. */ public class ReportDialogFragment extends DialogFragment { private static final String KEY_NUMBER = "number"; -- cgit v1.2.3