diff options
Diffstat (limited to 'java/com/android/dialer')
6 files changed, 233 insertions, 88 deletions
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index b9ca67f62..b5ec20246 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -124,7 +124,7 @@ import com.android.dialer.performancereport.PerformanceReport; import com.android.dialer.postcall.PostCall; import com.android.dialer.proguard.UsedByReflection; import com.android.dialer.searchfragment.list.NewSearchFragment; -import com.android.dialer.searchfragment.list.NewSearchFragment.SearchFragmentListTouchListener; +import com.android.dialer.searchfragment.list.NewSearchFragment.SearchFragmentListener; import com.android.dialer.simulator.Simulator; import com.android.dialer.simulator.SimulatorComponent; import com.android.dialer.smartdial.SmartDialNameMatcher; @@ -164,7 +164,7 @@ public class DialtactsActivity extends TransactionSafeActivity PhoneNumberInteraction.DisambigDialogDismissedListener, ActivityCompat.OnRequestPermissionsResultCallback, DialpadListener, - SearchFragmentListTouchListener { + SearchFragmentListener { public static final boolean DEBUG = false; @VisibleForTesting public static final String TAG_DIALPAD_FRAGMENT = "dialpad"; @@ -1664,6 +1664,14 @@ public class DialtactsActivity extends TransactionSafeActivity return false; } + @Override + public void onCallPlaced() { + if (mIsDialpadShown) { + hideDialpadFragment(false, true); + } + exitSearchUi(); + } + /** Popup menu accessible from the search bar */ protected class OptionsPopupMenu extends PopupMenu { diff --git a/java/com/android/dialer/logging/LoggingBindings.java b/java/com/android/dialer/logging/LoggingBindings.java index ca9a0533e..7682c767d 100644 --- a/java/com/android/dialer/logging/LoggingBindings.java +++ b/java/com/android/dialer/logging/LoggingBindings.java @@ -20,6 +20,13 @@ import android.widget.QuickContactBadge; /** Allows the container application to gather analytics. */ public interface LoggingBindings { + String ON_CREATE_PRIMES_EVENT_NAME = "Application.onCreate"; + String ON_CREATE_TO_ACTIVITY_ON_CREATE_PRIMES_EVENT_NAME = + "Application.onCreate_To_GoogleDialtactsActivity.onCreate"; + String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING = + "CallList.onCallAdded_To_InCallActivity.onCreate_Incoming"; + String ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING = + "CallList.onCallAdded_To_InCallActivity.onCreate_Outgoing"; /** * Logs an DialerImpression event that's not associated with a specific call. * @@ -87,4 +94,10 @@ public interface LoggingBindings { /** Logs successful People Api lookup result */ void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode); + + /** Log start a latency timer */ + void logStartLatencyTimer(String timerEventName); + + /** Log end a latency timer */ + void logStopLatencyTimer(String timerEventName); } diff --git a/java/com/android/dialer/logging/LoggingBindingsStub.java b/java/com/android/dialer/logging/LoggingBindingsStub.java index 2dbcc3ffb..ab6872cf4 100644 --- a/java/com/android/dialer/logging/LoggingBindingsStub.java +++ b/java/com/android/dialer/logging/LoggingBindingsStub.java @@ -61,4 +61,10 @@ public class LoggingBindingsStub implements LoggingBindings { @Override public void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode) {} + + @Override + public void logStartLatencyTimer(String timerEventName) {} + + @Override + public void logStopLatencyTimer(String timerEventName) {} } diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java index 323ec7c65..a3d97c64e 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java @@ -16,21 +16,138 @@ package com.android.dialer.phonelookup.cp2; +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.DeletedContacts; +import android.support.v4.util.ArraySet; import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.common.concurrent.DialerExecutors; import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.Set; -/** TODO(calderwoodra) */ -final class Cp2PhoneLookup implements PhoneLookup { +/** PhoneLookup implementation for local contacts. */ +public final class Cp2PhoneLookup implements PhoneLookup { + + private final Context context; + + Cp2PhoneLookup(Context context) { + this.context = context; + } @Override public ListenableFuture<Boolean> isDirty( ImmutableSet<DialerPhoneNumber> phoneNumbers, long lastModified) { - // TODO(calderwoodra) - return null; + // TODO(calderwoodra): consider a different thread pool + return MoreExecutors.listeningDecorator(DialerExecutors.getLowPriorityThreadPool(context)) + .submit(() -> isDirtyInternal(phoneNumbers, lastModified)); + } + + private boolean isDirtyInternal(ImmutableSet<DialerPhoneNumber> phoneNumbers, long lastModified) { + return contactsUpdated(getContactIdsFromPhoneNumbers(phoneNumbers), lastModified) + || contactsDeleted(lastModified); + } + + /** Returns set of contact ids that correspond to {@code phoneNumbers} if the contact exists. */ + private Set<Long> getContactIdsFromPhoneNumbers(ImmutableSet<DialerPhoneNumber> phoneNumbers) { + Set<Long> contactIds = new ArraySet<>(); + try (Cursor cursor = + context + .getContentResolver() + .query( + Phone.CONTENT_URI, + new String[] {Phone.CONTACT_ID}, + columnInSetWhereStatement(Phone.NORMALIZED_NUMBER, phoneNumbers.size()), + contactIdsSelectionArgs(phoneNumbers), + null)) { + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + contactIds.add(cursor.getLong(0 /* columnIndex */)); + } + } + return contactIds; + } + + private static String[] contactIdsSelectionArgs(ImmutableSet<DialerPhoneNumber> phoneNumbers) { + String[] args = new String[phoneNumbers.size()]; + int i = 0; + for (DialerPhoneNumber phoneNumber : phoneNumbers) { + args[i++] = getNormalizedNumber(phoneNumber); + } + return args; + } + + private static String getNormalizedNumber(DialerPhoneNumber phoneNumber) { + // TODO(calderwoodra): implement normalization logic that matches contacts. + return phoneNumber.getRawInput().getNumber(); + } + + /** Returns true if any contacts were modified after {@code lastModified}. */ + private boolean contactsUpdated(Set<Long> contactIds, long lastModified) { + try (Cursor cursor = + context + .getContentResolver() + .query( + Contacts.CONTENT_URI, + new String[] {Contacts._ID}, + contactsIsDirtyWhereStatement(contactIds.size()), + contactsIsDirtySelectionArgs(lastModified, contactIds), + null)) { + return cursor.getCount() > 0; + } + } + + private static String contactsIsDirtyWhereStatement(int numberOfContactIds) { + StringBuilder where = new StringBuilder(); + // Filter to after last modified time + where.append(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP).append(" > ?"); + + // Filter based only on contacts we care about + where.append(" AND ").append(columnInSetWhereStatement(Contacts._ID, numberOfContactIds)); + return where.toString(); + } + + private String[] contactsIsDirtySelectionArgs(long lastModified, Set<Long> contactIds) { + String[] args = new String[contactIds.size() + 1]; + args[0] = Long.toString(lastModified); + int i = 1; + for (Long contactId : contactIds) { + args[i++] = Long.toString(contactId); + } + return args; + } + + /** Returns true if any contacts were deleted after {@code lastModified}. */ + private boolean contactsDeleted(long lastModified) { + try (Cursor cursor = + context + .getContentResolver() + .query( + DeletedContacts.CONTENT_URI, + new String[] {DeletedContacts.CONTACT_DELETED_TIMESTAMP}, + DeletedContacts.CONTACT_DELETED_TIMESTAMP + " > ?", + new String[] {Long.toString(lastModified)}, + null)) { + return cursor.getCount() > 0; + } + } + + private static String columnInSetWhereStatement(String columnName, int setSize) { + StringBuilder where = new StringBuilder(); + where.append(columnName).append(" IN ("); + for (int i = 0; i < setSize; i++) { + if (i != 0) { + where.append(", "); + } + where.append("?"); + } + return where.append(")").toString(); } @Override diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java index ef1b4fc19..c200b946c 100644 --- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java +++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.ACCESS_FINE_LOCATION; import android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; +import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.database.Cursor; @@ -31,6 +32,7 @@ import android.support.annotation.VisibleForTesting; import android.support.v13.app.FragmentCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -42,13 +44,23 @@ import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; import com.android.contacts.common.extensions.PhoneDirectoryExtenderAccessor; import com.android.dialer.animation.AnimUtils; +import com.android.dialer.assisteddialing.ConcreteCreator; +import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.callintent.CallInitiationType; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.common.Assert; import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.dialer.constants.ActivityRequestCodes; +import com.android.dialer.dialercontact.DialerContact; +import com.android.dialer.duo.DuoComponent; import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener; +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.SearchContactsCursorLoader; import com.android.dialer.searchfragment.list.SearchActionViewHolder.Action; @@ -72,7 +84,8 @@ public final class NewSearchFragment extends Fragment implements LoaderCallbacks<Cursor>, OnEmptyViewActionButtonClickedListener, CapabilitiesListener, - OnTouchListener { + OnTouchListener, + RowClickListener { // 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. @@ -124,9 +137,8 @@ public final class NewSearchFragment extends Fragment public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_search, parent, false); - adapter = new SearchAdapter(getActivity(), new SearchCursorManager()); + adapter = new SearchAdapter(getContext(), new SearchCursorManager(), this); adapter.setQuery(query); - adapter.setCallInitiationType(callInitiationType); adapter.setSearchActions(getActions()); adapter.setZeroSuggestVisible(getArguments().getBoolean(KEY_SHOW_ZERO_SUGGEST)); emptyContentView = view.findViewById(R.id.empty_view); @@ -241,7 +253,6 @@ public final class NewSearchFragment extends Fragment this.callInitiationType = callInitiationType; if (adapter != null) { adapter.setQuery(query); - adapter.setCallInitiationType(callInitiationType); adapter.setSearchActions(getActions()); adapter.setZeroSuggestVisible(isRegularSearch()); loadNearbyPlacesCursor(); @@ -449,12 +460,59 @@ public final class NewSearchFragment extends Fragment if (event.getAction() == MotionEvent.ACTION_UP) { v.performClick(); } - return FragmentUtils.getParentUnsafe(this, SearchFragmentListTouchListener.class) + return FragmentUtils.getParentUnsafe(this, SearchFragmentListener.class) .onSearchListTouch(event); } - /** Callback to {@link NewSearchFragment}'s parent to notify when the list is touched. */ - public interface SearchFragmentListTouchListener { + @Override + public void placeVoiceCall(String phoneNumber, int ranking) { + placeCall(phoneNumber, ranking, false, true); + } + + @Override + public void placeVideoCall(String phoneNumber, int ranking) { + placeCall(phoneNumber, ranking, true, false); + } + + private void placeCall( + String phoneNumber, int position, boolean isVideoCall, boolean allowAssistedDial) { + CallSpecificAppData callSpecificAppData = + CallSpecificAppData.newBuilder() + .setCallInitiationType(callInitiationType) + .setPositionOfSelectedSearchResult(position) + .setCharactersInSearchString(query == null ? 0 : query.length()) + .setAllowAssistedDialing(allowAssistedDial) + .build(); + Intent intent = + new CallIntentBuilder(phoneNumber, callSpecificAppData) + .setIsVideoCall(isVideoCall) + .setAllowAssistedDial( + allowAssistedDial, + ConcreteCreator.createNewAssistedDialingMediator( + getContext().getSystemService(TelephonyManager.class), + getContext().getApplicationContext())) + .build(); + DialerUtils.startActivityWithErrorToast(getActivity(), intent); + FragmentUtils.getParentUnsafe(this, SearchFragmentListener.class).onCallPlaced(); + } + + @Override + public void placeDuoCall(String phoneNumber) { + Logger.get(getContext()) + .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_SEARCH); + Intent intent = DuoComponent.get(getContext()).getDuo().getIntent(getContext(), phoneNumber); + getActivity().startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO); + FragmentUtils.getParentUnsafe(this, SearchFragmentListener.class).onCallPlaced(); + } + + @Override + public void openCallAndShare(DialerContact contact) { + Intent intent = CallComposerActivity.newIntent(getContext(), contact); + DialerUtils.startActivityWithErrorToast(getContext(), intent); + } + + /** Callback to {@link NewSearchFragment}'s parent to be notified of important events. */ + public interface SearchFragmentListener { /** * Called when the list view in {@link NewSearchFragment} is touched. @@ -462,5 +520,8 @@ public final class NewSearchFragment extends Fragment * @see OnTouchListener#onTouch(View, MotionEvent) */ boolean onSearchListTouch(MotionEvent event); + + /** Called when a call is placed from the search fragment. */ + void onCallPlaced(); } } diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java index 949c2a2c5..dc78953b5 100644 --- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java +++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java @@ -16,55 +16,43 @@ package com.android.dialer.searchfragment.list; -import android.app.Activity; -import android.content.Intent; +import android.content.Context; import android.support.annotation.VisibleForTesting; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; -import android.telephony.TelephonyManager; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import com.android.dialer.assisteddialing.ConcreteCreator; -import com.android.dialer.callcomposer.CallComposerActivity; -import com.android.dialer.callintent.CallInitiationType; -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.duo.DuoComponent; -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; import java.util.List; /** RecyclerView adapter for {@link NewSearchFragment}. */ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) -public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> - implements RowClickListener { +public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> { private final SearchCursorManager searchCursorManager; - private final Activity activity; + private final Context context; private boolean showZeroSuggest; private String query; - private CallInitiationType.Type callInitiationType = CallInitiationType.Type.UNKNOWN_INITIATION; private OnClickListener allowClickListener; private OnClickListener dismissClickListener; + private RowClickListener rowClickListener; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public SearchAdapter(Activity activity, SearchCursorManager searchCursorManager) { - this.activity = activity; + public SearchAdapter( + Context context, SearchCursorManager searchCursorManager, RowClickListener rowClickListener) { + this.context = context; this.searchCursorManager = searchCursorManager; + this.rowClickListener = rowClickListener; } @Override @@ -72,24 +60,25 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> switch (rowType) { case RowType.CONTACT_ROW: return new SearchContactViewHolder( - LayoutInflater.from(activity).inflate(R.layout.search_contact_row, root, false), this); + LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false), + rowClickListener); case RowType.NEARBY_PLACES_ROW: return new NearbyPlaceViewHolder( - LayoutInflater.from(activity).inflate(R.layout.search_contact_row, root, false)); + LayoutInflater.from(context).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(activity).inflate(R.layout.header_layout, root, false)); + LayoutInflater.from(context).inflate(R.layout.header_layout, root, false)); case RowType.DIRECTORY_ROW: return new RemoteContactViewHolder( - LayoutInflater.from(activity).inflate(R.layout.search_contact_row, root, false)); + LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false)); case RowType.SEARCH_ACTION: return new SearchActionViewHolder( - LayoutInflater.from(activity).inflate(R.layout.search_action_layout, root, false)); + LayoutInflater.from(context).inflate(R.layout.search_action_layout, root, false)); case RowType.LOCATION_REQUEST: return new LocationPermissionViewHolder( - LayoutInflater.from(activity).inflate(R.layout.location_permission_row, root, false), + LayoutInflater.from(context).inflate(R.layout.location_permission_row, root, false), allowClickListener, dismissClickListener); case RowType.INVALID: @@ -168,10 +157,6 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> } } - void setCallInitiationType(CallInitiationType.Type callInitiationType) { - this.callInitiationType = callInitiationType; - } - public void setNearbyPlacesCursor(SearchCursor nearbyPlacesCursor) { if (searchCursorManager.setNearbyPlacesCursor(nearbyPlacesCursor)) { notifyDataSetChanged(); @@ -209,51 +194,6 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> } } - @Override - public void placeVoiceCall(String phoneNumber, int ranking) { - placeCall(phoneNumber, ranking, false, true); - } - - @Override - public void placeVideoCall(String phoneNumber, int ranking) { - placeCall(phoneNumber, ranking, true, false); - } - - private void placeCall( - String phoneNumber, int position, boolean isVideoCall, boolean allowAssistedDial) { - CallSpecificAppData callSpecificAppData = - CallSpecificAppData.newBuilder() - .setCallInitiationType(callInitiationType) - .setPositionOfSelectedSearchResult(position) - .setCharactersInSearchString(query == null ? 0 : query.length()) - .setAllowAssistedDialing(allowAssistedDial) - .build(); - Intent intent = - new CallIntentBuilder(phoneNumber, callSpecificAppData) - .setIsVideoCall(isVideoCall) - .setAllowAssistedDial( - allowAssistedDial, - ConcreteCreator.createNewAssistedDialingMediator( - activity.getSystemService(TelephonyManager.class), - activity.getApplicationContext())) - .build(); - DialerUtils.startActivityWithErrorToast(activity, intent); - } - - @Override - public void placeDuoCall(String phoneNumber) { - Logger.get(activity) - .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_SEARCH); - Intent intent = DuoComponent.get(activity).getDuo().getIntent(activity, phoneNumber); - activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO); - } - - @Override - public void openCallAndShare(DialerContact contact) { - Intent intent = CallComposerActivity.newIntent(activity, contact); - DialerUtils.startActivityWithErrorToast(activity, intent); - } - /** Viewholder for R.layout.location_permission_row that requests the location permission. */ private static class LocationPermissionViewHolder extends RecyclerView.ViewHolder { |