From dcefa65cf08671b748d549a2cdd169c5d2530415 Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Mon, 14 Aug 2017 12:07:47 -0700 Subject: Added ability to place RCS, Duo and IMS calls from new search fragment. Bug: 37209462 Test: SearchAdapterTest + existing tests PiperOrigin-RevId: 165210817 Change-Id: I9fb78cf7d964b97e6e95c01437780aa66405f019 --- java/com/android/dialer/app/DialtactsActivity.java | 14 +-- .../dialer/app/calllog/CallLogActivity.java | 3 +- .../app/calllog/CallLogListItemViewHolder.java | 7 +- .../dialer/constants/ActivityRequestCodes.java | 41 +++++++ .../dialer/searchfragment/common/Projections.java | 6 +- .../searchfragment/common/RowClickListener.java | 43 +++++++ .../cp2/SearchContactViewHolder.java | 133 +++++++++++++++++---- .../searchfragment/list/NewSearchFragment.java | 36 +++++- .../dialer/searchfragment/list/SearchAdapter.java | 79 ++++++++++-- .../searchfragment/list/SearchCursorManager.java | 11 +- 10 files changed, 321 insertions(+), 52 deletions(-) create mode 100644 java/com/android/dialer/constants/ActivityRequestCodes.java create mode 100644 java/com/android/dialer/searchfragment/common/RowClickListener.java (limited to 'java/com/android/dialer') diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 7e62065ee..a8b75bba7 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -97,6 +97,7 @@ import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.configprovider.ConfigProviderBindings; +import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.database.Database; import com.android.dialer.database.DialerDatabaseHelper; import com.android.dialer.interactions.PhoneNumberInteraction; @@ -172,11 +173,6 @@ public class DialtactsActivity extends TransactionSafeActivity /** Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. */ private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; - private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1; - public static final int ACTIVITY_REQUEST_CODE_CALL_COMPOSE = 2; - public static final int ACTIVITY_REQUEST_CODE_LIGHTBRINGER = 3; - public static final int ACTIVITY_REQUEST_CODE_CALL_DETAILS = 4; - private static final int FAB_SCALE_IN_DELAY_MS = 300; /** @@ -723,7 +719,7 @@ public class DialtactsActivity extends TransactionSafeActivity try { startActivityForResult( new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), - ACTIVITY_REQUEST_CODE_VOICE_SEARCH); + ActivityRequestCodes.DIALTACTS_VOICE_SEARCH); } catch (ActivityNotFoundException e) { Toast.makeText( DialtactsActivity.this, R.string.voice_search_not_available, Toast.LENGTH_SHORT) @@ -769,7 +765,7 @@ public class DialtactsActivity extends TransactionSafeActivity "requestCode:%d, resultCode:%d", requestCode, resultCode); - if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { + if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) { if (resultCode == RESULT_OK) { final ArrayList matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); @@ -781,7 +777,7 @@ public class DialtactsActivity extends TransactionSafeActivity } else { LogUtil.e("DialtactsActivity.onActivityResult", "voice search failed"); } - } else if (requestCode == ACTIVITY_REQUEST_CODE_CALL_COMPOSE) { + } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_COMPOSER) { if (resultCode == RESULT_FIRST_USER) { LogUtil.i( "DialtactsActivity.onActivityResult", "returned from call composer, error occurred"); @@ -793,7 +789,7 @@ public class DialtactsActivity extends TransactionSafeActivity } else { LogUtil.i("DialtactsActivity.onActivityResult", "returned from call composer, no error"); } - } else if (requestCode == ACTIVITY_REQUEST_CODE_CALL_DETAILS) { + } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_DETAILS) { if (resultCode == RESULT_OK && data != null && data.getBooleanExtra(CallDetailsActivity.EXTRA_HAS_ENRICHED_CALL_DATA, false)) { diff --git a/java/com/android/dialer/app/calllog/CallLogActivity.java b/java/com/android/dialer/app/calllog/CallLogActivity.java index c9e655d17..1bb894c59 100644 --- a/java/com/android/dialer/app/calllog/CallLogActivity.java +++ b/java/com/android/dialer/app/calllog/CallLogActivity.java @@ -33,6 +33,7 @@ import com.android.contacts.common.list.ViewPagerTabs; import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; import com.android.dialer.calldetails.CallDetailsActivity; +import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.database.CallLogQueryHandler; import com.android.dialer.logging.Logger; import com.android.dialer.logging.ScreenEvent; @@ -234,7 +235,7 @@ public class CallLogActivity extends TransactionSafeActivity @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == DialtactsActivity.ACTIVITY_REQUEST_CODE_CALL_DETAILS) { + if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_DETAILS) { if (resultCode == RESULT_OK && data != null && data.getBooleanExtra(CallDetailsActivity.EXTRA_HAS_ENRICHED_CALL_DATA, false)) { diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java index f7ea63c90..745f8b665 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java @@ -69,6 +69,7 @@ import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.CompatUtils; import com.android.dialer.configprovider.ConfigProviderBindings; +import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.contactphoto.ContactPhotoManager; import com.android.dialer.dialercontact.DialerContact; import com.android.dialer.dialercontact.SimDetails; @@ -867,7 +868,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder Activity activity = (Activity) mContext; activity.startActivityForResult( CallComposerActivity.newIntent(activity, buildContact()), - DialtactsActivity.ACTIVITY_REQUEST_CODE_CALL_COMPOSE); + ActivityRequestCodes.DIALTACTS_CALL_COMPOSER); } else if (view.getId() == R.id.share_voicemail) { Logger.get(mContext).logImpression(DialerImpression.Type.VVM_SHARE_PRESSED); mVoicemailPlaybackPresenter.shareVoicemail(); @@ -895,7 +896,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } else if (CallDetailsActivity.isLaunchIntent(intent)) { PerformanceReport.recordClick(UiAction.Type.OPEN_CALL_DETAIL); ((Activity) mContext) - .startActivityForResult(intent, DialtactsActivity.ACTIVITY_REQUEST_CODE_CALL_DETAILS); + .startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_CALL_DETAILS); } else { if (Intent.ACTION_CALL.equals(intent.getAction()) && intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, -1) @@ -911,7 +912,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder private void startLightbringerActivity(Intent intent) { try { Activity activity = (Activity) mContext; - activity.startActivityForResult(intent, DialtactsActivity.ACTIVITY_REQUEST_CODE_LIGHTBRINGER); + activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_LIGHTBRINGER); } catch (ActivityNotFoundException e) { Toast.makeText(mContext, R.string.activity_not_available, Toast.LENGTH_SHORT).show(); } diff --git a/java/com/android/dialer/constants/ActivityRequestCodes.java b/java/com/android/dialer/constants/ActivityRequestCodes.java new file mode 100644 index 000000000..da05eb76b --- /dev/null +++ b/java/com/android/dialer/constants/ActivityRequestCodes.java @@ -0,0 +1,41 @@ +/* + * 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.constants; + +/** + * Class containing {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)} + * request codes. + */ +public final class ActivityRequestCodes { + + private ActivityRequestCodes() {} + + /** Request code for {@link android.speech.RecognizerIntent#ACTION_RECOGNIZE_SPEECH} intent. */ + public static final int DIALTACTS_VOICE_SEARCH = 1; + + /** Request code for {@link com.android.dialer.callcomposer.CallComposerActivity} intent. */ + public static final int DIALTACTS_CALL_COMPOSER = 2; + + /** + * Request code for {@link + * com.android.dialer.lightbringer.Lightbringer#getIntent(android.content.Context, String)}. + */ + public static final int DIALTACTS_LIGHTBRINGER = 3; + + /** Request code for {@link com.android.dialer.calldetails.CallDetailsActivity} intent. */ + public static final int DIALTACTS_CALL_DETAILS = 4; +} diff --git a/java/com/android/dialer/searchfragment/common/Projections.java b/java/com/android/dialer/searchfragment/common/Projections.java index 37e20d195..078c3e5e6 100644 --- a/java/com/android/dialer/searchfragment/common/Projections.java +++ b/java/com/android/dialer/searchfragment/common/Projections.java @@ -30,9 +30,10 @@ public class Projections { public static final int PHONE_PHOTO_URI = 6; public static final int PHONE_LOOKUP_KEY = 7; public static final int PHONE_CARRIER_PRESENCE = 8; + public static final int PHONE_CONTACT_ID = 9; @SuppressWarnings("unused") - public static final int PHONE_SORT_KEY = 9; + public static final int PHONE_SORT_KEY = 10; public static final String[] PHONE_PROJECTION = new String[] { @@ -45,6 +46,7 @@ public class Projections { Phone.PHOTO_THUMBNAIL_URI, // 6 Phone.LOOKUP_KEY, // 7 Phone.CARRIER_PRESENCE, // 8 - Phone.SORT_KEY_PRIMARY // 9 + Phone.CONTACT_ID, // 9 + Phone.SORT_KEY_PRIMARY // 10 }; } diff --git a/java/com/android/dialer/searchfragment/common/RowClickListener.java b/java/com/android/dialer/searchfragment/common/RowClickListener.java new file mode 100644 index 000000000..e82f3f7bb --- /dev/null +++ b/java/com/android/dialer/searchfragment/common/RowClickListener.java @@ -0,0 +1,43 @@ +/* + * 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.searchfragment.common; + +import com.android.dialer.dialercontact.DialerContact; + +/** Interface of possible actions that can be performed by search elements. */ +public interface RowClickListener { + + /** + * Places a traditional voice call. + * + * @param ranking position in the list relative to the other elements + */ + void placeVoiceCall(String phoneNumber, int ranking); + + /** + * Places an IMS video call. + * + * @param ranking position in the list relative to the other elements + */ + void placeVideoCall(String phoneNumber, int ranking); + + /** Places a Duo video call. */ + void placeDuoCall(String phoneNumber); + + /** Opens the enriched calling/call composer interface. */ + void openCallAndShare(DialerContact dialerContact); +} diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java index 1e8224ddb..327fe5303 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java @@ -23,6 +23,7 @@ import android.net.Uri; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView.ViewHolder; import android.text.TextUtils; import android.view.View; @@ -30,16 +31,19 @@ import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.QuickContactBadge; import android.widget.TextView; -import com.android.dialer.callintent.CallInitiationType.Type; -import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.common.Assert; import com.android.dialer.contactphoto.ContactPhotoManager; +import com.android.dialer.dialercontact.DialerContact; +import com.android.dialer.enrichedcall.EnrichedCallCapabilities; +import com.android.dialer.enrichedcall.EnrichedCallComponent; +import com.android.dialer.enrichedcall.EnrichedCallManager; import com.android.dialer.lettertile.LetterTileDrawable; +import com.android.dialer.lightbringer.LightbringerComponent; import com.android.dialer.searchfragment.common.Projections; import com.android.dialer.searchfragment.common.QueryBoldingUtil; import com.android.dialer.searchfragment.common.R; +import com.android.dialer.searchfragment.common.RowClickListener; import com.android.dialer.searchfragment.common.SearchCursor; -import com.android.dialer.telecom.TelecomUtil; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -48,24 +52,34 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick /** IntDef for the different types of actions that can be shown. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({CallToAction.NONE, CallToAction.VIDEO_CALL, CallToAction.SHARE_AND_CALL}) + @IntDef({ + CallToAction.NONE, + CallToAction.VIDEO_CALL, + CallToAction.DUO_CALL, + CallToAction.SHARE_AND_CALL + }) @interface CallToAction { int NONE = 0; int VIDEO_CALL = 1; - int SHARE_AND_CALL = 2; + int DUO_CALL = 2; + int SHARE_AND_CALL = 3; } + private final RowClickListener listener; private final QuickContactBadge photo; private final TextView nameOrNumberView; private final TextView numberView; private final ImageView callToActionView; private final Context context; + private int position; private String number; + private DialerContact dialerContact; private @CallToAction int currentAction; - public SearchContactViewHolder(View view) { + public SearchContactViewHolder(View view, RowClickListener listener) { super(view); + this.listener = listener; view.setOnClickListener(this); photo = view.findViewById(R.id.photo); nameOrNumberView = view.findViewById(R.id.primary); @@ -79,6 +93,8 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick * at the cursors set position. */ public void bind(SearchCursor cursor, String query) { + dialerContact = getDialerContact(context, cursor); + position = cursor.getPosition(); number = cursor.getString(Projections.PHONE_NUMBER); String name = cursor.getString(Projections.PHONE_DISPLAY_NAME); String label = getLabel(context.getResources(), cursor); @@ -90,7 +106,7 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick nameOrNumberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name)); numberView.setText(QueryBoldingUtil.getNumberWithQueryBolded(query, secondaryInfo)); - setCallToAction(cursor); + setCallToAction(cursor, query); if (shouldShowPhoto(cursor)) { nameOrNumberView.setVisibility(View.VISIBLE); @@ -144,8 +160,8 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick return (String) Phone.getTypeLabel(resources, numberType, numberLabel); } - private void setCallToAction(Cursor cursor) { - currentAction = getCallToAction(cursor); + private void setCallToAction(SearchCursor cursor, String query) { + currentAction = getCallToAction(context, cursor, query); switch (currentAction) { case CallToAction.NONE: callToActionView.setVisibility(View.GONE); @@ -157,6 +173,7 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick context.getDrawable(com.android.contacts.common.R.drawable.ic_phone_attach)); callToActionView.setOnClickListener(this); break; + case CallToAction.DUO_CALL: case CallToAction.VIDEO_CALL: callToActionView.setVisibility(View.VISIBLE); callToActionView.setImageDrawable( @@ -169,31 +186,69 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick } } - private static @CallToAction int getCallToAction(Cursor cursor) { + private static @CallToAction int getCallToAction( + Context context, SearchCursor cursor, String query) { int carrierPresence = cursor.getInt(Projections.PHONE_CARRIER_PRESENCE); + String number = cursor.getString(Projections.PHONE_NUMBER); if ((carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) == 1) { return CallToAction.VIDEO_CALL; } - // TODO(calderwoodra): enriched calling + if (LightbringerComponent.get(context).getLightbringer().isReachable(context, number)) { + return CallToAction.DUO_CALL; + } + + EnrichedCallManager manager = EnrichedCallComponent.get(context).getEnrichedCallManager(); + EnrichedCallCapabilities capabilities = manager.getCapabilities(number); + if (capabilities != null && capabilities.isCallComposerCapable()) { + return CallToAction.SHARE_AND_CALL; + } else if (shouldRequestCapabilities(cursor, capabilities, query)) { + manager.requestCapabilities(number); + } return CallToAction.NONE; } + /** + * An RPC is initiated for each number we request capabilities for, so to limit the network load + * and latency on slow networks, we only want to request capabilities for potential contacts the + * user is interested in calling. The requirements are that: + * + * + */ + private static boolean shouldRequestCapabilities( + SearchCursor cursor, + @Nullable EnrichedCallCapabilities capabilities, + @Nullable String query) { + if (capabilities != null) { + return false; + } + + if (query != null && query.length() >= 3) { + return true; + } + + // TODO(calderwoodra): implement SearchCursor#getHeaderCount + if (cursor.getCount() <= 5) { // 4 contacts + 1 header row element + return true; + } + return false; + } + @Override public void onClick(View view) { if (view == callToActionView) { switch (currentAction) { case CallToAction.SHARE_AND_CALL: - callToActionView.setVisibility(View.VISIBLE); - callToActionView.setImageDrawable( - context.getDrawable(com.android.contacts.common.R.drawable.ic_phone_attach)); - // TODO(calderwoodra): open call composer. + listener.openCallAndShare(dialerContact); break; case CallToAction.VIDEO_CALL: - callToActionView.setVisibility(View.VISIBLE); - callToActionView.setImageDrawable( - context.getDrawable(R.drawable.quantum_ic_videocam_white_24)); - // TODO(calderwoodra): place a video call + listener.placeVideoCall(number, position); + break; + case CallToAction.DUO_CALL: + listener.placeDuoCall(number); break; case CallToAction.NONE: default: @@ -201,8 +256,44 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick "Invalid Call to action type: " + currentAction); } } else { - // TODO(calderwoodra): set the correct call initiation type. - TelecomUtil.placeCall(context, new CallIntentBuilder(number, Type.REGULAR_SEARCH).build()); + listener.placeVoiceCall(number, position); + } + } + + private static DialerContact getDialerContact(Context context, Cursor cursor) { + DialerContact.Builder contact = DialerContact.newBuilder(); + String displayName = cursor.getString(Projections.PHONE_DISPLAY_NAME); + String number = cursor.getString(Projections.PHONE_NUMBER); + Uri contactUri = + Contacts.getLookupUri( + cursor.getLong(Projections.PHONE_CONTACT_ID), + cursor.getString(Projections.PHONE_LOOKUP_KEY)); + + contact + .setNumber(number) + .setPhotoId(cursor.getLong(Projections.PHONE_PHOTO_ID)) + .setContactType(LetterTileDrawable.TYPE_DEFAULT) + .setNameOrNumber(displayName) + .setNumberLabel( + Phone.getTypeLabel( + context.getResources(), + cursor.getInt(Projections.PHONE_TYPE), + cursor.getString(Projections.PHONE_LABEL)) + .toString()); + + String photoUri = cursor.getString(Projections.PHONE_PHOTO_URI); + if (photoUri != null) { + contact.setPhotoUri(photoUri); } + + if (contactUri != null) { + contact.setContactUri(contactUri.toString()); + } + + if (!TextUtils.isEmpty(displayName)) { + contact.setDisplayNumber(number); + } + + return contact.build(); } } diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java index 2c0281536..7d355c91c 100644 --- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java +++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java @@ -37,6 +37,8 @@ import com.android.dialer.animation.AnimUtils; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.dialer.enrichedcall.EnrichedCallComponent; +import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener; import com.android.dialer.searchfragment.common.SearchCursor; import com.android.dialer.searchfragment.cp2.SearchContactsCursorLoader; import com.android.dialer.searchfragment.nearbyplaces.NearbyPlacesCursorLoader; @@ -53,11 +55,16 @@ import java.util.List; /** Fragment used for searching contacts. */ public final class NewSearchFragment extends Fragment - implements LoaderCallbacks, OnEmptyViewActionButtonClickedListener { + implements LoaderCallbacks, + OnEmptyViewActionButtonClickedListener, + CapabilitiesListener { // Since some of our queries can generate network requests, we should delay them until the user // stops typing to prevent generating too much network traffic. private static final int NETWORK_SEARCH_DELAY_MILLIS = 300; + // To prevent constant capabilities updates refreshing the adapter, we want to add a delay between + // updates so they are bundled together + private static final int ENRICHED_CALLING_CAPABILITIES_UPDATED_DELAY = 400; @VisibleForTesting public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; @@ -77,6 +84,7 @@ public final class NewSearchFragment extends Fragment () -> getLoaderManager().restartLoader(NEARBY_PLACES_LOADER_ID, null, this); private final Runnable loadRemoteContactsRunnable = () -> getLoaderManager().restartLoader(REMOTE_CONTACTS_LOADER_ID, null, this); + private final Runnable capabilitiesUpdatedRunnable = () -> adapter.notifyDataSetChanged(); private Runnable updatePositionRunnable; @@ -85,7 +93,7 @@ public final class NewSearchFragment extends Fragment public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle bundle) { View view = inflater.inflate(R.layout.fragment_search, parent, false); - adapter = new SearchAdapter(getContext(), new SearchCursorManager()); + adapter = new SearchAdapter(getActivity(), new SearchCursorManager()); emptyContentView = view.findViewById(R.id.empty_view); recyclerView = view.findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); @@ -192,6 +200,7 @@ public final class NewSearchFragment extends Fragment super.onDestroy(); ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable); ThreadUtil.getUiThreadHandler().removeCallbacks(loadRemoteContactsRunnable); + ThreadUtil.getUiThreadHandler().removeCallbacks(capabilitiesUpdatedRunnable); } private void loadNearbyPlacesCursor() { @@ -249,6 +258,29 @@ public final class NewSearchFragment extends Fragment .postDelayed(loadRemoteContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS); } + @Override + public void onResume() { + super.onResume(); + EnrichedCallComponent.get(getContext()) + .getEnrichedCallManager() + .registerCapabilitiesListener(this); + } + + @Override + public void onPause() { + super.onPause(); + EnrichedCallComponent.get(getContext()) + .getEnrichedCallManager() + .unregisterCapabilitiesListener(this); + } + + @Override + public void onCapabilitiesUpdated() { + ThreadUtil.getUiThreadHandler().removeCallbacks(capabilitiesUpdatedRunnable); + ThreadUtil.getUiThreadHandler() + .postDelayed(capabilitiesUpdatedRunnable, ENRICHED_CALLING_CAPABILITIES_UPDATED_DELAY); + } + // Currently, setting up multiple FakeContentProviders doesn't work and results in this fragment // being untestable while it can query multiple datasources. This is a temporary fix. // TODO(b/64099602): Remove this method and test this fragment with multiple data sources diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java index 81e8e38f7..4cb44a2db 100644 --- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java +++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java @@ -16,28 +16,45 @@ package com.android.dialer.searchfragment.list; -import android.content.Context; +import android.app.Activity; +import android.content.Intent; +import android.support.annotation.VisibleForTesting; 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.callcomposer.CallComposerActivity; +import com.android.dialer.callintent.CallInitiationType; +import com.android.dialer.callintent.CallInitiationType.Type; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.common.Assert; +import com.android.dialer.constants.ActivityRequestCodes; +import com.android.dialer.dialercontact.DialerContact; +import com.android.dialer.lightbringer.LightbringerComponent; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; +import com.android.dialer.searchfragment.common.RowClickListener; import com.android.dialer.searchfragment.common.SearchCursor; import com.android.dialer.searchfragment.cp2.SearchContactViewHolder; import com.android.dialer.searchfragment.list.SearchCursorManager.RowType; import com.android.dialer.searchfragment.nearbyplaces.NearbyPlaceViewHolder; import com.android.dialer.searchfragment.remote.RemoteContactViewHolder; +import com.android.dialer.util.DialerUtils; /** RecyclerView adapter for {@link NewSearchFragment}. */ -class SearchAdapter extends RecyclerView.Adapter { +@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) +public final class SearchAdapter extends RecyclerView.Adapter + implements RowClickListener { private final SearchCursorManager searchCursorManager; - private final Context context; + private final Activity activity; private String query; - SearchAdapter(Context context, SearchCursorManager searchCursorManager) { - this.context = context; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public SearchAdapter(Activity activity, SearchCursorManager searchCursorManager) { + this.activity = activity; this.searchCursorManager = searchCursorManager; } @@ -46,18 +63,18 @@ class SearchAdapter extends RecyclerView.Adapter { switch (rowType) { case RowType.CONTACT_ROW: return new SearchContactViewHolder( - LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false)); + LayoutInflater.from(activity).inflate(R.layout.search_contact_row, root, false), this); case RowType.NEARBY_PLACES_ROW: return new NearbyPlaceViewHolder( - LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false)); + LayoutInflater.from(activity).inflate(R.layout.search_contact_row, root, false)); case RowType.CONTACT_HEADER: case RowType.DIRECTORY_HEADER: case RowType.NEARBY_PLACES_HEADER: return new HeaderViewHolder( - LayoutInflater.from(context).inflate(R.layout.header_layout, root, false)); + LayoutInflater.from(activity).inflate(R.layout.header_layout, root, false)); case RowType.DIRECTORY_ROW: return new RemoteContactViewHolder( - LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false)); + LayoutInflater.from(activity).inflate(R.layout.search_contact_row, root, false)); case RowType.INVALID: default: throw Assert.createIllegalStateFailException("Invalid RowType: " + rowType); @@ -86,7 +103,7 @@ class SearchAdapter extends RecyclerView.Adapter { } } - void setContactsCursor(SearchCursor cursor) { + public void setContactsCursor(SearchCursor cursor) { searchCursorManager.setContactsCursor(cursor); notifyDataSetChanged(); } @@ -118,4 +135,46 @@ class SearchAdapter extends RecyclerView.Adapter { notifyDataSetChanged(); } } + + @Override + public void placeVoiceCall(String phoneNumber, int ranking) { + placeCall(phoneNumber, ranking, false); + } + + @Override + public void placeVideoCall(String phoneNumber, int ranking) { + placeCall(phoneNumber, ranking, true); + } + + private void placeCall(String phoneNumber, int position, boolean isVideoCall) { + CallSpecificAppData callSpecificAppData = + CallSpecificAppData.newBuilder() + .setCallInitiationType(getCallInitiationType()) + .setPositionOfSelectedSearchResult(position) + .setCharactersInSearchString(query == null ? 0 : query.length()) + .build(); + Intent intent = + new CallIntentBuilder(phoneNumber, callSpecificAppData).setIsVideoCall(isVideoCall).build(); + DialerUtils.startActivityWithErrorToast(activity, intent); + } + + @Override + public void placeDuoCall(String phoneNumber) { + Logger.get(activity) + .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_SEARCH); + Intent intent = + LightbringerComponent.get(activity).getLightbringer().getIntent(activity, phoneNumber); + activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_LIGHTBRINGER); + } + + @Override + public void openCallAndShare(DialerContact contact) { + Intent intent = CallComposerActivity.newIntent(activity, contact); + DialerUtils.startActivityWithErrorToast(activity, intent); + } + + private CallInitiationType.Type getCallInitiationType() { + // TODO(calderwoodra): add correct initiation type + return Type.REGULAR_SEARCH; + } } diff --git a/java/com/android/dialer/searchfragment/list/SearchCursorManager.java b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java index b385aa392..a303425d3 100644 --- a/java/com/android/dialer/searchfragment/list/SearchCursorManager.java +++ b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java @@ -17,6 +17,8 @@ package com.android.dialer.searchfragment.list; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import com.android.dialer.common.Assert; import com.android.dialer.searchfragment.common.SearchCursor; import java.lang.annotation.Retention; @@ -42,7 +44,8 @@ import java.lang.annotation.RetentionPolicy; *
  • {@link #getRowType(int)} * */ -final class SearchCursorManager { +@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) +public final class SearchCursorManager { /** IntDef for the different types of rows that can be shown when searching. */ @Retention(RetentionPolicy.SOURCE) @@ -77,7 +80,7 @@ final class SearchCursorManager { private SearchCursor corpDirectoryCursor = null; /** Returns true if the cursor changed. */ - boolean setContactsCursor(SearchCursor cursor) { + boolean setContactsCursor(@Nullable SearchCursor cursor) { if (cursor == contactsCursor) { return false; } @@ -95,7 +98,7 @@ final class SearchCursorManager { } /** Returns true if the cursor changed. */ - boolean setNearbyPlacesCursor(SearchCursor cursor) { + boolean setNearbyPlacesCursor(@Nullable SearchCursor cursor) { if (cursor == nearbyPlacesCursor) { return false; } @@ -113,7 +116,7 @@ final class SearchCursorManager { } /** Returns true if a cursor changed. */ - boolean setCorpDirectoryCursor(SearchCursor cursor) { + boolean setCorpDirectoryCursor(@Nullable SearchCursor cursor) { if (cursor == corpDirectoryCursor) { return false; } -- cgit v1.2.3