From 529bf0f551afa5d7a17d6806e86110f4fe9e100b Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Thu, 14 Dec 2017 00:51:11 -0800 Subject: Implemented disambig dialog for new speed dial fragment. This change implements the logic to build the disambig dialog and display the relevant information. Future CLs will: - Implement the logic for favoriting a communication avenue - Polish the UI Bug: 36841782 Test: DisambigDialogTest PiperOrigin-RevId: 179012593 Change-Id: I4294f4ae2a475b1e560eabd424e54a829c0d7829 --- .../android/dialer/speeddial/DisambigDialog.java | 244 +++++++++++++++++++++ .../dialer/speeddial/FavoritesViewHolder.java | 16 +- .../dialer/speeddial/SpeedDialFragment.java | 5 + .../speeddial/StrequentContactsCursorLoader.java | 7 - .../res/layout/disambig_dialog_layout.xml | 66 ++++++ .../res/layout/disambig_option_layout.xml | 103 +++++++++ .../android/dialer/speeddial/res/values/dimens.xml | 1 + .../dialer/speeddial/res/values/strings.xml | 14 ++ 8 files changed, 447 insertions(+), 9 deletions(-) create mode 100644 java/com/android/dialer/speeddial/DisambigDialog.java create mode 100644 java/com/android/dialer/speeddial/res/layout/disambig_dialog_layout.xml create mode 100644 java/com/android/dialer/speeddial/res/layout/disambig_option_layout.xml (limited to 'java/com/android/dialer/speeddial') diff --git a/java/com/android/dialer/speeddial/DisambigDialog.java b/java/com/android/dialer/speeddial/DisambigDialog.java new file mode 100644 index 000000000..ca02f41eb --- /dev/null +++ b/java/com/android/dialer/speeddial/DisambigDialog.java @@ -0,0 +1,244 @@ +/* + * 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.speeddial; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.ContentResolver; +import android.content.res.Resources; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; +import android.util.ArraySet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.android.dialer.callintent.CallInitiationType; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import com.android.dialer.common.concurrent.DialerExecutorComponent; +import com.android.dialer.duo.DuoComponent; +import com.android.dialer.precall.PreCall; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; + +/** Disambiguation dialog for favorite contacts in {@link SpeedDialFragment}. */ +public class DisambigDialog extends DialogFragment { + + @VisibleForTesting public static final String DISAMBIG_DIALOG_TAG = "disambig_dialog"; + private static final String DISAMBIG_DIALOG_WORKER_TAG = "disambig_dialog_worker"; + + private final Set phoneNumbers = new ArraySet<>(); + private LinearLayout container; + private String lookupKey; + + /** Show a disambiguation dialog for a starred contact without a favorite communication avenue. */ + public static DisambigDialog show(String lookupKey, FragmentManager manager) { + DisambigDialog dialog = new DisambigDialog(); + dialog.lookupKey = lookupKey; + dialog.show(manager, DISAMBIG_DIALOG_TAG); + return dialog; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + LayoutInflater inflater = getActivity().getLayoutInflater(); + View view = inflater.inflate(R.layout.disambig_dialog_layout, null, false); + container = view.findViewById(R.id.communication_avenue_container); + return new AlertDialog.Builder(getActivity()).setView(view).create(); + } + + @Override + public void onResume() { + super.onResume(); + lookupContactInfo(); + } + + @Override + public void onPause() { + super.onPause(); + // TODO(calderwoodra): for simplicity, just dismiss the dialog on configuration change and + // consider changing this later. + dismiss(); + } + + private void lookupContactInfo() { + DialerExecutorComponent.get(getContext()) + .dialerExecutorFactory() + .createUiTaskBuilder( + getFragmentManager(), + DISAMBIG_DIALOG_WORKER_TAG, + new LookupContactInfoWorker(getContext().getContentResolver())) + .onSuccess(this::insertOptions) + .onFailure(this::onLookupFailed) + .build() + .executeParallel(lookupKey); + } + + /** + * Inflates and inserts the following in the dialog: + * + * + */ + private void insertOptions(Cursor cursor) { + if (!cursorIsValid(cursor)) { + dismiss(); + return; + } + + do { + String number = cursor.getString(LookupContactInfoWorker.NUMBER_INDEX); + // TODO(calderwoodra): improve this to include fuzzy matching + if (phoneNumbers.add(number)) { + insertOption( + number, + getLabel(getContext().getResources(), cursor), + isVideoReachable(cursor, number)); + } + } while (cursor.moveToNext()); + cursor.close(); + // TODO(calderwoodra): set max height of the scrollview. Might need to override onMeasure. + } + + /** Returns true if the given number is ViLTE reachable or Duo reachable. */ + private boolean isVideoReachable(Cursor cursor, String number) { + boolean isVideoReachable = cursor.getInt(LookupContactInfoWorker.PHONE_PRESENCE_INDEX) == 1; + if (!isVideoReachable) { + isVideoReachable = DuoComponent.get(getContext()).getDuo().isReachable(getContext(), number); + } + return isVideoReachable; + } + + /** Inserts a group of options for a specific phone number. */ + private void insertOption(String number, String phoneType, boolean isVideoReachable) { + View view = + getActivity() + .getLayoutInflater() + .inflate(R.layout.disambig_option_layout, container, false); + ((TextView) view.findViewById(R.id.phone_type)).setText(phoneType); + ((TextView) view.findViewById(R.id.phone_number)).setText(number); + + if (isVideoReachable) { + View videoOption = view.findViewById(R.id.video_call_container); + videoOption.setOnClickListener(v -> onVideoOptionClicked(number)); + videoOption.setVisibility(View.VISIBLE); + } + View voiceOption = view.findViewById(R.id.voice_call_container); + voiceOption.setOnClickListener(v -> onVoiceOptionClicked(number)); + container.addView(view); + } + + private void onVideoOptionClicked(String number) { + // TODO(calderwoodra): save this option if remember is checked + // TODO(calderwoodra): place a duo call if possible + PreCall.start( + getContext(), + new CallIntentBuilder(number, CallInitiationType.Type.SPEED_DIAL).setIsVideoCall(true)); + } + + private void onVoiceOptionClicked(String number) { + // TODO(calderwoodra): save this option if remember is checked + PreCall.start(getContext(), new CallIntentBuilder(number, CallInitiationType.Type.SPEED_DIAL)); + } + + // TODO(calderwoodra): handle CNAP and cequint types. + // TODO(calderwoodra): unify this into a utility method with CallLogAdapter#getNumberType + private static String getLabel(Resources resources, Cursor cursor) { + int numberType = cursor.getInt(LookupContactInfoWorker.PHONE_TYPE_INDEX); + String numberLabel = cursor.getString(LookupContactInfoWorker.PHONE_LABEL_INDEX); + + // Returns empty label instead of "custom" if the custom label is empty. + if (numberType == Phone.TYPE_CUSTOM && TextUtils.isEmpty(numberLabel)) { + return ""; + } + return (String) Phone.getTypeLabel(resources, numberType, numberLabel); + } + + // Checks if the cursor is valid and logs an error if there are any issues. + private static boolean cursorIsValid(Cursor cursor) { + if (cursor == null) { + LogUtil.e("DisambigDialog.insertOptions", "cursor null."); + return false; + } else if (cursor.isClosed()) { + LogUtil.e("DisambigDialog.insertOptions", "cursor closed."); + cursor.close(); + return false; + } else if (!cursor.moveToFirst()) { + LogUtil.e("DisambigDialog.insertOptions", "cursor empty."); + cursor.close(); + return false; + } + return true; + } + + private void onLookupFailed(Throwable throwable) { + LogUtil.e("DisambigDialog.onLookupFailed", null, throwable); + insertOptions(null); + } + + private static class LookupContactInfoWorker implements Worker { + + static final int NUMBER_INDEX = 0; + static final int PHONE_TYPE_INDEX = 1; + static final int PHONE_LABEL_INDEX = 2; + static final int PHONE_PRESENCE_INDEX = 3; + + private static final String[] projection = + new String[] {Phone.NUMBER, Phone.TYPE, Phone.LABEL, Phone.CARRIER_PRESENCE}; + private final ContentResolver resolver; + + LookupContactInfoWorker(ContentResolver resolver) { + this.resolver = resolver; + } + + @Nullable + @Override + public Cursor doInBackground(@Nullable String lookupKey) throws Throwable { + if (TextUtils.isEmpty(lookupKey)) { + LogUtil.e("LookupConctactInfoWorker.doInBackground", "contact id unsest."); + return null; + } + return resolver.query( + Phone.CONTENT_URI, projection, Phone.LOOKUP_KEY + " = ?", new String[] {lookupKey}, null); + } + } + + @VisibleForTesting + public static String[] getProjectionForTesting() { + ArrayList projection = + new ArrayList<>(Arrays.asList(LookupContactInfoWorker.projection)); + projection.add(Phone.LOOKUP_KEY); + return projection.toArray(new String[projection.size()]); + } + + @VisibleForTesting + public LinearLayout getContainer() { + return container; + } +} diff --git a/java/com/android/dialer/speeddial/FavoritesViewHolder.java b/java/com/android/dialer/speeddial/FavoritesViewHolder.java index 0cde71693..c25b05ead 100644 --- a/java/com/android/dialer/speeddial/FavoritesViewHolder.java +++ b/java/com/android/dialer/speeddial/FavoritesViewHolder.java @@ -45,8 +45,10 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder private final TextView phoneType; private final FrameLayout videoCallIcon; + private boolean hasDefaultNumber; private boolean isVideoCall; private String number; + private String lookupKey; public FavoritesViewHolder(View view, FavoriteContactsListener listener) { super(view); @@ -67,7 +69,7 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder String name = cursor.getString(StrequentContactsCursorLoader.PHONE_DISPLAY_NAME); long contactId = cursor.getLong(StrequentContactsCursorLoader.PHONE_ID); - String lookupKey = cursor.getString(StrequentContactsCursorLoader.PHONE_LOOKUP_KEY); + lookupKey = cursor.getString(StrequentContactsCursorLoader.PHONE_LOOKUP_KEY); Uri contactUri = Contacts.getLookupUri(contactId, lookupKey); String photoUri = cursor.getString(StrequentContactsCursorLoader.PHONE_PHOTO_URI); @@ -82,6 +84,9 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder nameView.setText(name); phoneType.setText(getLabel(context.getResources(), cursor)); videoCallIcon.setVisibility(isVideoCall ? View.VISIBLE : View.GONE); + + // TODO(calderwoodra): Update this to include communication avenues also + hasDefaultNumber = cursor.getInt(StrequentContactsCursorLoader.PHONE_IS_SUPER_PRIMARY) != 0; } // TODO(calderwoodra): handle CNAP and cequint types. @@ -99,7 +104,11 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder @Override public void onClick(View v) { - listener.onClick(number, isVideoCall); + if (hasDefaultNumber) { + listener.onClick(number, isVideoCall); + } else { + listener.onAmbiguousContactClicked(lookupKey); + } } @Override @@ -112,6 +121,9 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder /** Listener/callback for {@link FavoritesViewHolder} actions. */ public interface FavoriteContactsListener { + /** Called when the user clicks on a favorite contact that doesn't have a default number. */ + void onAmbiguousContactClicked(String contactId); + /** Called when the user clicks on a favorite contact. */ void onClick(String number, boolean isVideoCall); diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java index 08861dae9..979c894fe 100644 --- a/java/com/android/dialer/speeddial/SpeedDialFragment.java +++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java @@ -97,6 +97,11 @@ public class SpeedDialFragment extends Fragment { private class SpeedDialFavoritesListener implements FavoriteContactsListener { + @Override + public void onAmbiguousContactClicked(String lookupKey) { + DisambigDialog.show(lookupKey, getFragmentManager()); + } + @Override public void onClick(String number, boolean isVideoCall) { // TODO(calderwoodra): add logic for duo video calls diff --git a/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java b/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java index f5f0045e0..e9e3e32da 100644 --- a/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java +++ b/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java @@ -18,7 +18,6 @@ package com.android.dialer.speeddial; import android.content.Context; import android.content.CursorLoader; -import android.database.ContentObserver; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; @@ -58,8 +57,6 @@ final class StrequentContactsCursorLoader extends CursorLoader { Phone.CONTACT_ID, // 11 }; - private final ContentObserver contentObserver = new ForceLoadContentObserver(); - StrequentContactsCursorLoader(Context context) { super( context, @@ -97,8 +94,4 @@ final class StrequentContactsCursorLoader extends CursorLoader { public Cursor loadInBackground() { return SpeedDialCursor.newInstance(super.loadInBackground()); } - - ContentObserver getContentObserver() { - return contentObserver; - } } diff --git a/java/com/android/dialer/speeddial/res/layout/disambig_dialog_layout.xml b/java/com/android/dialer/speeddial/res/layout/disambig_dialog_layout.xml new file mode 100644 index 000000000..356205805 --- /dev/null +++ b/java/com/android/dialer/speeddial/res/layout/disambig_dialog_layout.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/speeddial/res/layout/disambig_option_layout.xml b/java/com/android/dialer/speeddial/res/layout/disambig_option_layout.xml new file mode 100644 index 000000000..097ac4084 --- /dev/null +++ b/java/com/android/dialer/speeddial/res/layout/disambig_option_layout.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/speeddial/res/values/dimens.xml b/java/com/android/dialer/speeddial/res/values/dimens.xml index 5929df8dd..74b509b73 100644 --- a/java/com/android/dialer/speeddial/res/values/dimens.xml +++ b/java/com/android/dialer/speeddial/res/values/dimens.xml @@ -15,4 +15,5 @@ ~ limitations under the License --> + 280dp \ No newline at end of file diff --git a/java/com/android/dialer/speeddial/res/values/strings.xml b/java/com/android/dialer/speeddial/res/values/strings.xml index d64d03575..677f772c5 100644 --- a/java/com/android/dialer/speeddial/res/values/strings.xml +++ b/java/com/android/dialer/speeddial/res/values/strings.xml @@ -24,6 +24,20 @@ Add + + Remember this choice + + + Choose a Favorite mode + + + Video call + + + Call + Add Favorite \ No newline at end of file -- cgit v1.2.3