Add Assisted Dialing Call Details Implementation.

This includes all of the work specified in the UX layouts,
with the exception of the parenthetical country code.

Because the canonical mapping of dial prefixes to country
codes is not contained in this package, some thought needs to
be given on the best way to implement this part of the spec.

This change also introduces a fallback error text, in case the
client experiences bad data from the call log.

Test: new unit tests
PiperOrigin-RevId: 178038123
Change-Id: I88eec72a73820e092f24c5f53ee9520a42486ada
diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java
index b51d833..c29f9e9 100644
--- a/java/com/android/dialer/calldetails/CallDetailsActivity.java
+++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java
@@ -33,13 +33,20 @@
 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.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.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;
@@ -51,10 +58,12 @@
 import com.android.dialer.logging.Logger;
 import com.android.dialer.logging.UiAction;
 import com.android.dialer.performancereport.PerformanceReport;
+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.Preconditions;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
 import java.lang.ref.WeakReference;
 import java.util.Collections;
 import java.util.List;
@@ -70,8 +79,8 @@
   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";
 
-  private final CallDetailsHeaderViewHolder.CallbackActionListener callbackActionListener =
-      new CallbackActionListener(this);
+  private final CallDetailsHeaderViewHolder.CallDetailsHeaderListener callDetailsHeaderListener =
+      new CallDetailsHeaderListener(this);
   private final CallDetailsFooterViewHolder.DeleteCallDetailsListener deleteCallDetailsListener =
       new DeleteCallDetailsListener(this);
   private final CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener =
@@ -166,7 +175,7 @@
             this /* context */,
             contact,
             entries.getEntriesList(),
-            callbackActionListener,
+            callDetailsHeaderListener,
             reportCallIdListener,
             deleteCallDetailsListener);
 
@@ -248,11 +257,11 @@
     }
   }
 
-  private static final class CallbackActionListener
-      implements CallDetailsHeaderViewHolder.CallbackActionListener {
-    private final WeakReference<Activity> activityWeakReference;
+  private static final class CallDetailsHeaderListener
+      implements CallDetailsHeaderViewHolder.CallDetailsHeaderListener {
+    private final WeakReference<CallDetailsActivity> activityWeakReference;
 
-    CallbackActionListener(Activity activity) {
+    CallDetailsHeaderListener(CallDetailsActivity activity) {
       this.activityWeakReference = new WeakReference<>(activity);
     }
 
@@ -303,9 +312,43 @@
       PreCall.start(getActivity(), callIntentBuilder);
     }
 
-    private Activity getActivity() {
+    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<Integer> 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<String, Integer> {
+
+    @Override
+    public Integer doInBackground(@NonNull String phoneNumber) {
+      DialerPhoneNumberUtil dialerPhoneNumberUtil =
+          new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance());
+      DialerPhoneNumber parsedNumber = dialerPhoneNumberUtil.parse(phoneNumber, null);
+      return parsedNumber.getDialerInternalPhoneNumber().getCountryCode();
+    }
   }
 
   private static final class DeleteCallDetailsListener
diff --git a/java/com/android/dialer/calldetails/CallDetailsAdapter.java b/java/com/android/dialer/calldetails/CallDetailsAdapter.java
index 0759059..9095b86 100644
--- a/java/com/android/dialer/calldetails/CallDetailsAdapter.java
+++ b/java/com/android/dialer/calldetails/CallDetailsAdapter.java
@@ -24,7 +24,7 @@
 import android.view.ViewGroup;
 import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
 import com.android.dialer.calldetails.CallDetailsFooterViewHolder.DeleteCallDetailsListener;
-import com.android.dialer.calldetails.CallDetailsHeaderViewHolder.CallbackActionListener;
+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;
@@ -41,7 +41,7 @@
   private static final int FOOTER_VIEW_TYPE = 3;
 
   private final DialerContact contact;
-  private final CallbackActionListener callbackActionListener;
+  private final CallDetailsHeaderListener callDetailsHeaderListener;
   private final CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener;
   private final DeleteCallDetailsListener deleteCallDetailsListener;
   private final CallTypeHelper callTypeHelper;
@@ -51,12 +51,12 @@
       Context context,
       @NonNull DialerContact contact,
       @NonNull List<CallDetailsEntry> callDetailsEntries,
-      CallbackActionListener callbackActionListener,
+      CallDetailsHeaderListener callDetailsHeaderListener,
       CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener,
       DeleteCallDetailsListener deleteCallDetailsListener) {
     this.contact = Assert.isNotNull(contact);
     this.callDetailsEntries = callDetailsEntries;
-    this.callbackActionListener = callbackActionListener;
+    this.callDetailsHeaderListener = callDetailsHeaderListener;
     this.reportCallIdListener = reportCallIdListener;
     this.deleteCallDetailsListener = deleteCallDetailsListener;
     callTypeHelper = new CallTypeHelper(context.getResources(), DuoComponent.get(context).getDuo());
@@ -68,7 +68,7 @@
     switch (viewType) {
       case HEADER_VIEW_TYPE:
         return new CallDetailsHeaderViewHolder(
-            inflater.inflate(R.layout.contact_container, parent, false), callbackActionListener);
+            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));
@@ -87,6 +87,8 @@
   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 {
diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
index 1e08963..604a5e8 100644
--- a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
+++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
@@ -25,9 +25,16 @@
 import android.view.View.OnClickListener;
 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.CallDetailsEntries.CallDetailsEntry;
 import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction;
 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.compat.telephony.TelephonyManagerCompat;
 import com.android.dialer.contactphoto.ContactPhotoManager;
 import com.android.dialer.dialercontact.DialerContact;
 import com.android.dialer.logging.InteractionEvent;
@@ -35,20 +42,22 @@
 
 /** ViewHolder for Header/Contact in {@link CallDetailsActivity}. */
 public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder
-    implements OnClickListener {
+    implements OnClickListener, FailureListener {
 
-  private final CallbackActionListener callbackActionListener;
+  private final CallDetailsHeaderListener callDetailsHeaderListener;
   private final ImageView callbackButton;
   private final TextView nameView;
   private final TextView numberView;
   private final TextView networkView;
   private final QuickContactBadge contactPhoto;
   private final Context context;
+  private final TextView assistedDialingInternationalDirectDialCodeAndCountryCodeText;
+  private final RelativeLayout assistedDialingContainer;
 
   private DialerContact contact;
   private @CallbackAction int callbackAction;
 
-  CallDetailsHeaderViewHolder(View container, CallbackActionListener callbackActionListener) {
+  CallDetailsHeaderViewHolder(View container, CallDetailsHeaderListener callDetailsHeaderListener) {
     super(container);
     context = container.getContext();
     callbackButton = container.findViewById(R.id.call_back_button);
@@ -56,14 +65,71 @@
     numberView = container.findViewById(R.id.phone_number);
     networkView = container.findViewById(R.id.network);
     contactPhoto = container.findViewById(R.id.quick_contact_photo);
+    assistedDialingInternationalDirectDialCodeAndCountryCodeText =
+        container.findViewById(R.id.assisted_dialing_text);
+    assistedDialingContainer = container.findViewById(R.id.assisted_dialing_container);
+
+    assistedDialingContainer.setOnClickListener(
+        callDetailsHeaderListener::openAssistedDialingSettings);
 
     callbackButton.setOnClickListener(this);
-    this.callbackActionListener = callbackActionListener;
+    this.callDetailsHeaderListener = callDetailsHeaderListener;
     Logger.get(context)
         .logQuickContactOnTouch(
             contactPhoto, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CALL_DETAILS, true);
   }
 
+  private boolean hasAssistedDialingFeature(Integer features) {
+    return (features & TelephonyManagerCompat.FEATURES_ASSISTED_DIALING)
+        == TelephonyManagerCompat.FEATURES_ASSISTED_DIALING;
+  }
+
+  void updateAssistedDialingInfo(CallDetailsEntry callDetailsEntry) {
+
+    if (callDetailsEntry != null && hasAssistedDialingFeature(callDetailsEntry.getFeatures())) {
+      showAssistedDialingContainer(true);
+      callDetailsHeaderListener.createAssistedDialerNumberParserTask(
+          new CallDetailsActivity.AssistedDialingNumberParseWorker(),
+          this::updateAssistedDialingText,
+          this::onFailure);
+
+    } else {
+      showAssistedDialingContainer(false);
+    }
+  }
+
+  private void showAssistedDialingContainer(boolean shouldShowContainer) {
+    if (shouldShowContainer) {
+      assistedDialingContainer.setVisibility(View.VISIBLE);
+    } else {
+      LogUtil.i(
+          "CallDetailsHeaderViewHolder.updateAssistedDialingInfo",
+          "hiding assisted dialing ui elements");
+      assistedDialingContainer.setVisibility(View.GONE);
+    }
+  }
+
+  private void updateAssistedDialingText(Integer countryCode) {
+
+    // Try and handle any poorly formed inputs.
+    if (countryCode <= 0) {
+      onFailure(new IllegalStateException());
+      return;
+    }
+
+    LogUtil.i(
+        "CallDetailsHeaderViewHolder.updateAssistedDialingText", "Updating Assisted Dialing Text");
+    assistedDialingInternationalDirectDialCodeAndCountryCodeText.setText(
+        context.getString(
+            R.string.assisted_dialing_country_code_entry, String.valueOf(countryCode)));
+  }
+
+  @Override
+  public void onFailure(Throwable unused) {
+    assistedDialingInternationalDirectDialCodeAndCountryCodeText.setText(
+        R.string.assisted_dialing_country_code_entry_failure);
+  }
+
   /** Populates the contact info fields based on the current contact information. */
   void updateContactInfo(DialerContact contact, @CallbackAction int callbackAction) {
     this.contact = contact;
@@ -128,13 +194,14 @@
     if (view == callbackButton) {
       switch (callbackAction) {
         case CallbackAction.IMS_VIDEO:
-          callbackActionListener.placeImsVideoCall(contact.getNumber());
+          callDetailsHeaderListener.placeImsVideoCall(contact.getNumber());
           break;
         case CallbackAction.DUO:
-          callbackActionListener.placeDuoVideoCall(contact.getNumber());
+          callDetailsHeaderListener.placeDuoVideoCall(contact.getNumber());
           break;
         case CallbackAction.VOICE:
-          callbackActionListener.placeVoiceCall(contact.getNumber(), contact.getPostDialDigits());
+          callDetailsHeaderListener.placeVoiceCall(
+              contact.getNumber(), contact.getPostDialDigits());
           break;
         case CallbackAction.NONE:
         default:
@@ -145,8 +212,8 @@
     }
   }
 
-  /** Listener for making a callback */
-  interface CallbackActionListener {
+  /** Listener for the call details header */
+  interface CallDetailsHeaderListener {
 
     /** Places an IMS video call. */
     void placeImsVideoCall(String phoneNumber);
@@ -156,5 +223,13 @@
 
     /** Place a traditional voice call. */
     void placeVoiceCall(String phoneNumber, String postDialDigits);
+
+    /** Access the Assisted Dialing settings * */
+    void openAssistedDialingSettings(View view);
+
+    void createAssistedDialerNumberParserTask(
+        AssistedDialingNumberParseWorker worker,
+        SuccessListener<Integer> onSuccess,
+        FailureListener onFailure);
   }
 }
diff --git a/java/com/android/dialer/calldetails/res/layout/contact_container.xml b/java/com/android/dialer/calldetails/res/layout/contact_container.xml
index b01a6cc..5f531ab 100644
--- a/java/com/android/dialer/calldetails/res/layout/contact_container.xml
+++ b/java/com/android/dialer/calldetails/res/layout/contact_container.xml
@@ -19,49 +19,54 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginTop="@dimen/call_details_top_margin"
-    android:gravity="center_vertical"
     android:paddingTop="@dimen/contact_container_padding_top_start"
-    android:paddingStart="@dimen/contact_container_padding_top_start"
     android:paddingBottom="@dimen/contact_container_padding_bottom_end"
-    android:paddingEnd="@dimen/contact_container_padding_bottom_end">
+    android:paddingStart="@dimen/contact_container_padding_top_start"
+    android:paddingEnd="@dimen/contact_container_padding_bottom_end"
+    android:gravity="center_vertical"
+    android:orientation="vertical">
 
   <QuickContactBadge
       android:id="@+id/quick_contact_photo"
       android:layout_width="@dimen/call_details_contact_photo_size"
       android:layout_height="@dimen/call_details_contact_photo_size"
-      android:layout_centerVertical="true"
       android:padding="@dimen/call_details_contact_photo_padding"
       android:focusable="true"/>
 
   <LinearLayout
-      android:orientation="vertical"
+      android:id="@+id/contact_information"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
+      android:layout_alignParentTop="true"
       android:layout_toEndOf="@+id/quick_contact_photo"
       android:layout_toStartOf="@+id/call_back_button"
-      android:layout_centerVertical="true">
+      android:gravity="center_vertical"
+      android:minHeight="@dimen/call_details_contact_photo_size"
+      android:orientation="vertical">
 
     <TextView
         android:id="@+id/contact_name"
+        style="@style/PrimaryText"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/photo_text_margin"
-        style="@style/PrimaryText"/>
+        android:layout_marginStart="@dimen/photo_text_margin"/>
 
     <TextView
         android:id="@+id/phone_number"
+        style="@style/SecondaryText"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/photo_text_margin"
-        style="@style/SecondaryText"/>
+        android:layout_marginStart="@dimen/photo_text_margin"/>
 
     <TextView
         android:id="@+id/network"
+        style="@style/SecondaryText"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/photo_text_margin"
-        android:visibility="gone"
-        style="@style/SecondaryText"/>
+        android:visibility="gone"/>
+
+
   </LinearLayout>
 
   <ImageView
@@ -69,10 +74,40 @@
       android:layout_width="@dimen/call_back_button_size"
       android:layout_height="@dimen/call_back_button_size"
       android:layout_alignParentEnd="true"
-      android:layout_centerVertical="true"
       android:background="?android:attr/selectableItemBackgroundBorderless"
       android:contentDescription="@string/call"
       android:scaleType="center"
       android:src="@drawable/quantum_ic_call_white_24"
       android:tint="@color/secondary_text_color"/>
+
+
+  <RelativeLayout
+      android:id="@+id/assisted_dialing_container"
+      android:layout_width="match_parent"
+      android:layout_height="@dimen/ad_container_height"
+      android:layout_below="@+id/contact_information"
+      android:background="?android:attr/selectableItemBackground"
+      android:gravity="center_vertical">
+
+    <ImageView
+        android:id="@+id/assisted_dialing_globe"
+        android:layout_width="@dimen/ad_icon_size"
+        android:layout_height="@dimen/ad_icon_size"
+        android:layout_marginTop="@dimen/ad_icon_margin_top_offset"
+        android:layout_marginStart="@dimen/ad_icon_margin_start_offset"
+        android:scaleType="fitCenter"
+        android:src="@drawable/quantum_ic_language_vd_theme_24"
+        android:tint="@color/secondary_text_color"/>
+
+    <TextView
+        android:id="@+id/assisted_dialing_text"
+        style="@style/SecondaryText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/ad_text_margin_start"
+        android:layout_marginEnd="@dimen/ad_end_margin"
+        android:layout_toRightOf="@id/assisted_dialing_globe"/>
+
+  </RelativeLayout>
+
 </RelativeLayout>
\ No newline at end of file
diff --git a/java/com/android/dialer/calldetails/res/values/dimens.xml b/java/com/android/dialer/calldetails/res/values/dimens.xml
index 694c8f4..8c84b1b 100644
--- a/java/com/android/dialer/calldetails/res/values/dimens.xml
+++ b/java/com/android/dialer/calldetails/res/values/dimens.xml
@@ -18,7 +18,7 @@
   <dimen name="call_details_top_margin">6dp</dimen>
 
   <!-- contact container -->
-  <dimen name="contact_container_padding_bottom_end">16dp</dimen>
+  <dimen name="contact_container_padding_bottom_end">8dp</dimen>
   <dimen name="contact_container_padding_top_start">12dp</dimen>
   <dimen name="call_details_contact_photo_size">48dp</dimen>
   <dimen name="call_details_contact_photo_padding">4dp</dimen>
@@ -34,4 +34,14 @@
   <dimen name="ec_container_height">48dp</dimen>
   <dimen name="ec_photo_size">40dp</dimen>
   <dimen name="ec_divider_top_bottom_margin">8dp</dimen>
+
+  <!-- Assisted Dialing -->
+  <dimen name="ad_container_height">48dp</dimen>
+  <dimen name="ad_icon_size">18dp</dimen>
+  <dimen name="ad_end_margin">16dp</dimen>
+  <dimen name="ad_text_margin_start">8dp</dimen>
+  <!-- Used to help smooth the text alignment to the center of the icon -->
+  <dimen name="ad_icon_margin_top_offset">2dp</dimen>
+  <dimen name="ad_icon_margin_start_offset">60dp</dimen>
+
 </resources>
\ No newline at end of file
diff --git a/java/com/android/dialer/calldetails/res/values/strings.xml b/java/com/android/dialer/calldetails/res/values/strings.xml
index 74ac71c..f816960 100644
--- a/java/com/android/dialer/calldetails/res/values/strings.xml
+++ b/java/com/android/dialer/calldetails/res/values/strings.xml
@@ -49,4 +49,10 @@
 
   <!-- Toast message which appears when a contact's caller id is reported as incorrect. [CHAR LIMIT=NONE] -->
   <string name="report_caller_id_toast">Number reported</string>
+
+  <!-- Assisted dialing section header. [CHAR LIMIT=NONE] -->
+  <string name="assisted_dialing_country_code_entry">Assisted dialing: Used country code +<xliff:g example="1" id="ad_country_code">%1$s</xliff:g></string>
+
+  <!-- A fallback string for the assisted dialing header incase parsing failes.. [CHAR LIMIT=NONE] -->
+  <string name="assisted_dialing_country_code_entry_failure">Assisted dialing was used</string>
 </resources>
diff --git a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java
index cadce4d..6bed818 100644
--- a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java
+++ b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java
@@ -77,6 +77,12 @@
       BuildCompat.isAtLeastOMR1() ? "android.telephony.extra.IS_REFRESH" : "is_refresh";
 
   /**
+   * Indicates the call underwent Assisted Dialing; typically set as a feature available from the
+   * CallLog.
+   */
+  public static final Integer FEATURES_ASSISTED_DIALING = 0x10;
+
+  /**
    * Returns the number of phones available. Returns 1 for Single standby mode (Single SIM
    * functionality) Returns 2 for Dual standby mode.(Dual SIM functionality)
    *