From a98ac7f0b74d6f993e9ef2a7f1ad094d1027d712 Mon Sep 17 00:00:00 2001 From: linyuh Date: Wed, 13 Dec 2017 10:12:56 -0800 Subject: Integrate CallDetailsActivity with the new call log UI. Bug: 70218437 Test: CallDetailsActivityTest, CallDetailsCursorLoaderTest, ModulesTest PiperOrigin-RevId: 178918820 Change-Id: Ib8034190550e8ca8e6e7fd9ce521bfadc73e834f --- .../dialer/calldetails/CallDetailsActivity.java | 107 ++++++++++++++-- .../dialer/calldetails/CallDetailsAdapter.java | 4 +- .../calldetails/CallDetailsCursorLoader.java | 139 +++++++++++++++++++++ .../android/dialer/calllog/ui/menu/Modules.java | 14 +-- 4 files changed, 246 insertions(+), 18 deletions(-) create mode 100644 java/com/android/dialer/calldetails/CallDetailsCursorLoader.java (limited to 'java/com') diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java index c29f9e9ae..b314e26bf 100644 --- a/java/com/android/dialer/calldetails/CallDetailsActivity.java +++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java @@ -19,9 +19,12 @@ 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; @@ -35,11 +38,13 @@ 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.DialerPhoneNumber; 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.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.AsyncTaskExecutors; @@ -62,6 +67,7 @@ import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; 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.PhoneNumberUtil; import java.lang.ref.WeakReference; @@ -71,10 +77,12 @@ 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"; + 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"; private static final String EXTRA_CAN_SUPPORT_ASSISTED_DIALING = "can_support_assisted_dialing"; @@ -93,23 +101,47 @@ public class CallDetailsActivity extends AppCompatActivity { 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(); + public static boolean isLaunchIntent(Intent intent) { return intent.getComponent() != null && CallDetailsActivity.class.getName().equals(intent.getComponent().getClassName()); } + /** + * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the old call log + * UI. + */ public static Intent newInstance( Context context, - @NonNull CallDetailsEntries details, - @NonNull DialerContact contact, + CallDetailsEntries details, + DialerContact contact, boolean canReportCallerId, boolean canSupportAssistedDialing) { - Assert.isNotNull(details); - Assert.isNotNull(contact); + 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; + } + /** + * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the new call log + * UI. + */ + public static Intent newInstance( + Context context, + CoalescedIds coalescedAnnotatedCallLogIds, + DialerContact contact, + boolean canReportCallerId, + boolean canSupportAssistedDialing) { Intent intent = new Intent(context, CallDetailsActivity.class); - ProtoParsers.put(intent, EXTRA_CONTACT, contact); - ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, details); + ProtoParsers.put(intent, EXTRA_CONTACT, Assert.isNotNull(contact)); + ProtoParsers.put( + intent, EXTRA_COALESCED_CALL_LOG_IDS, Assert.isNotNull(coalescedAnnotatedCallLogIds)); intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId); intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing); return intent; @@ -166,10 +198,30 @@ public class CallDetailsActivity extends AppCompatActivity { } 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()); - entries = - ProtoParsers.getTrusted( - intent, EXTRA_CALL_DETAILS_ENTRIES, CallDetailsEntries.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 */, @@ -191,6 +243,43 @@ public class CallDetailsActivity extends AppCompatActivity { super.onBackPressed(); } + /** + * {@link LoaderCallbacks} for {@link CallDetailsCursorLoader}, which loads call detail entries + * from {@link AnnotatedCallLog}. + */ + private static final class CallDetailsLoaderCallbacks implements LoaderCallbacks { + private final CallDetailsActivity activity; + + CallDetailsLoaderCallbacks(CallDetailsActivity callDetailsActivity) { + this.activity = callDetailsActivity; + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Assert.checkState(activity.coalescedCallLogIds.isPresent()); + + return new CallDetailsCursorLoader(activity, activity.coalescedCallLogIds.get()); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + updateCallDetailsEntries(CallDetailsCursorLoader.toCallDetailsEntries(data)); + } + + @Override + public void onLoaderReset(Loader loader) { + updateCallDetailsEntries(CallDetailsEntries.getDefaultInstance()); + } + + private void updateCallDetailsEntries(CallDetailsEntries newEntries) { + activity.entries = newEntries; + activity.adapter.updateCallDetailsEntries(newEntries.getEntriesList()); + 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. diff --git a/java/com/android/dialer/calldetails/CallDetailsAdapter.java b/java/com/android/dialer/calldetails/CallDetailsAdapter.java index 9095b86ea..030366e9f 100644 --- a/java/com/android/dialer/calldetails/CallDetailsAdapter.java +++ b/java/com/android/dialer/calldetails/CallDetailsAdapter.java @@ -115,7 +115,9 @@ final class CallDetailsAdapter extends RecyclerView.Adapter entries) { diff --git a/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java b/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java new file mode 100644 index 000000000..838525372 --- /dev/null +++ b/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java @@ -0,0 +1,139 @@ +/* + * 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.CursorLoader; +import android.database.Cursor; +import com.android.dialer.CoalescedIds; +import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; +import com.android.dialer.common.Assert; +import com.android.dialer.duo.DuoConstants; + +/** + * A {@link CursorLoader} that loads call detail entries from {@link AnnotatedCallLog} for {@link + * CallDetailsActivity}. + */ +public final class CallDetailsCursorLoader extends CursorLoader { + + // Columns in AnnotatedCallLog that are needed to build a CallDetailsEntry proto. + // Be sure to update (1) constants that store indexes of the elements and (2) method + // toCallDetailsEntry(Cursor) when updating this array. + public static final String[] COLUMNS_FOR_CALL_DETAILS = + new String[] { + AnnotatedCallLog._ID, + AnnotatedCallLog.CALL_TYPE, + AnnotatedCallLog.FEATURES, + AnnotatedCallLog.TIMESTAMP, + AnnotatedCallLog.DURATION, + AnnotatedCallLog.DATA_USAGE, + AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME + }; + + // Indexes for COLUMNS_FOR_CALL_DETAILS + private static final int ID = 0; + private static final int CALL_TYPE = 1; + private static final int FEATURES = 2; + private static final int TIMESTAMP = 3; + private static final int DURATION = 4; + private static final int DATA_USAGE = 5; + private static final int PHONE_ACCOUNT_COMPONENT_NAME = 6; + + CallDetailsCursorLoader(Context context, CoalescedIds coalescedIds) { + super( + context, + AnnotatedCallLog.CONTENT_URI, + COLUMNS_FOR_CALL_DETAILS, + annotatedCallLogIdsSelection(coalescedIds), + annotatedCallLogIdsSelectionArgs(coalescedIds), + AnnotatedCallLog.TIMESTAMP + " DESC"); + } + + /** + * Build a string of the form "COLUMN_NAME IN (?, ?, ..., ?)", where COLUMN_NAME is the name of + * the ID column in {@link AnnotatedCallLog}. + * + *

This string will be used as the {@code selection} parameter to initialize the loader. + */ + private static String annotatedCallLogIdsSelection(CoalescedIds coalescedIds) { + // First, build a string of question marks ('?') separated by commas (','). + StringBuilder questionMarks = new StringBuilder(); + for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) { + if (i != 0) { + questionMarks.append(", "); + } + questionMarks.append("?"); + } + + return AnnotatedCallLog._ID + " IN (" + questionMarks + ")"; + } + + /** + * Returns a string that will be used as the {@code selectionArgs} parameter to initialize the + * loader. + */ + private static String[] annotatedCallLogIdsSelectionArgs(CoalescedIds coalescedIds) { + String[] args = new String[coalescedIds.getCoalescedIdCount()]; + + for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) { + args[i] = String.valueOf(coalescedIds.getCoalescedId(i)); + } + + return args; + } + + /** + * Creates a new {@link CallDetailsEntries} from the entire data set loaded by this loader. + * + * @param cursor A cursor pointing to the data set loaded by this loader. The caller must ensure + * the cursor is not null and the data set it points to is not empty. + * @return A {@link CallDetailsEntries} proto. + */ + static CallDetailsEntries toCallDetailsEntries(Cursor cursor) { + Assert.isNotNull(cursor); + Assert.checkArgument(cursor.moveToFirst()); + + CallDetailsEntries.Builder entries = CallDetailsEntries.newBuilder(); + + do { + entries.addEntries(toCallDetailsEntry(cursor)); + } while (cursor.moveToNext()); + + return entries.build(); + } + + /** Creates a new {@link CallDetailsEntry} from the provided cursor using its current position. */ + private static CallDetailsEntry toCallDetailsEntry(Cursor cursor) { + CallDetailsEntry.Builder entry = CallDetailsEntry.newBuilder(); + entry + .setCallId(cursor.getLong(ID)) + .setCallType(cursor.getInt(CALL_TYPE)) + .setFeatures(cursor.getInt(FEATURES)) + .setDate(cursor.getLong(TIMESTAMP)) + .setDuration(cursor.getLong(DURATION)) + .setDataUsage(cursor.getLong(DATA_USAGE)); + + String phoneAccountComponentName = cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME); + entry.setIsDuoCall( + DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME + .flattenToString() + .equals(phoneAccountComponentName)); + + return entry.build(); + } +} diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java index 550e28453..cccaa731d 100644 --- a/java/com/android/dialer/calllog/ui/menu/Modules.java +++ b/java/com/android/dialer/calllog/ui/menu/Modules.java @@ -24,7 +24,6 @@ import android.provider.ContactsContract; import android.telecom.PhoneAccountHandle; import android.text.TextUtils; import com.android.dialer.calldetails.CallDetailsActivity; -import com.android.dialer.calldetails.CallDetailsEntries; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.calllog.model.CoalescedRow; import com.android.dialer.calllogutils.PhoneAccountUtils; @@ -65,7 +64,7 @@ final class Modules { // TODO(zachh): Revisit if DialerContact is the best thing to pass to CallDetails; could // it use a ContactPrimaryActionInfo instead? - addModuleForAccessingCallDetails(context, createDialerContactFromRow(row), modules); + addModuleForAccessingCallDetails(context, row, modules); return modules; } @@ -182,10 +181,9 @@ final class Modules { } private static void addModuleForAccessingCallDetails( - Context context, DialerContact dialerContact, List modules) { - // TODO(zachh): Load CallDetailsEntries and canReportInaccurateNumber in - // CallDetailsActivity (see also isPeopleApiSource(sourceType)). - CallDetailsEntries callDetailsEntries = CallDetailsEntries.getDefaultInstance(); + Context context, CoalescedRow row, List modules) { + // TODO(zachh): Load canReportInaccurateNumber in CallDetailsActivity + // (see also isPeopleApiSource(sourceType)). boolean canReportInaccurateNumber = false; boolean canSupportAssistedDialing = false; // TODO(zachh): Properly set value. @@ -194,8 +192,8 @@ final class Modules { context, CallDetailsActivity.newInstance( context, - callDetailsEntries, - dialerContact, + row.coalescedIds(), + createDialerContactFromRow(row), canReportInaccurateNumber, canSupportAssistedDialing), R.string.call_details_menu_label, -- cgit v1.2.3