Implement VoicemailDataSource

This data source determines if the call is to the voicemail inbox.

isVoicemail() is removed from NumberAttributes and PhoneLookup. It is yet decided how in call UI should handle voicemail calls in the future.

TAG_CHANGE_OK=proto not in prod yet. Please clear app data.
TYPE_CHANGE_OK=above
Bug: 70989587
Test: Unit tests
PiperOrigin-RevId: 189650273
Change-Id: Iafebf1abb18c74301b62a72d1d04deecd6d78d29
diff --git a/java/com/android/dialer/calllog/CallLogModule.java b/java/com/android/dialer/calllog/CallLogModule.java
index 6c85fd6..9dd9a79 100644
--- a/java/com/android/dialer/calllog/CallLogModule.java
+++ b/java/com/android/dialer/calllog/CallLogModule.java
@@ -20,6 +20,7 @@
 import com.android.dialer.calllog.datasources.DataSources;
 import com.android.dialer.calllog.datasources.phonelookup.PhoneLookupDataSource;
 import com.android.dialer.calllog.datasources.systemcalllog.SystemCallLogDataSource;
+import com.android.dialer.calllog.datasources.voicemail.VoicemailDataSource;
 import com.google.common.collect.ImmutableList;
 import dagger.Module;
 import dagger.Provides;
@@ -31,10 +32,11 @@
   @Provides
   static DataSources provideCallLogDataSources(
       SystemCallLogDataSource systemCallLogDataSource,
-      PhoneLookupDataSource phoneLookupDataSource) {
+      PhoneLookupDataSource phoneLookupDataSource,
+      VoicemailDataSource voicemailDataSource) {
     // System call log must be first, see getDataSourcesExcludingSystemCallLog below.
     ImmutableList<CallLogDataSource> allDataSources =
-        ImmutableList.of(systemCallLogDataSource, phoneLookupDataSource);
+        ImmutableList.of(systemCallLogDataSource, phoneLookupDataSource, voicemailDataSource);
     return new DataSources() {
       @Override
       public SystemCallLogDataSource getSystemCallLogDataSource() {
diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
index c838737..3b67ff2 100644
--- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
+++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
@@ -56,6 +56,8 @@
           + (AnnotatedCallLog.VOICEMAIL_URI + " text, ")
           + (AnnotatedCallLog.CALL_TYPE + " integer not null, ")
           + (AnnotatedCallLog.NUMBER_ATTRIBUTES + " blob, ")
+          + (AnnotatedCallLog.IS_VOICEMAIL_CALL + " integer, ")
+          + (AnnotatedCallLog.VOICEMAIL_CALL_TAG + " text, ")
           + (AnnotatedCallLog.TRANSCRIPTION_STATE + " integer")
           + ");";
 
diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
index c181d75..b1cf6e4 100644
--- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
+++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
@@ -133,6 +133,25 @@
     String NUMBER_ATTRIBUTES = "number_attributes";
 
     /**
+     * Whether the call is to the voicemail inbox.
+     *
+     * <p>TYPE: INTEGER (boolean)
+     *
+     * @see android.telecom.TelecomManager#isVoiceMailNumber(android.telecom.PhoneAccountHandle,
+     *     String)
+     */
+    String IS_VOICEMAIL_CALL = "is_voicemail_call";
+
+    /**
+     * The "name" of the voicemail inbox. This is provided by the SIM to show as the caller ID
+     *
+     * <p>TYPE: TEXT
+     *
+     * @see android.telephony.TelephonyManager#getVoiceMailAlphaTag()
+     */
+    String VOICEMAIL_CALL_TAG = "voicemail_call_tag";
+
+    /**
      * Copied from {@link android.provider.CallLog.Calls#TYPE}.
      *
      * <p>Type: INTEGER (int)
@@ -155,6 +174,8 @@
           PHONE_ACCOUNT_COLOR,
           FEATURES,
           NUMBER_ATTRIBUTES,
+          IS_VOICEMAIL_CALL,
+          VOICEMAIL_CALL_TAG,
           CALL_TYPE
         };
   }
diff --git a/java/com/android/dialer/calllog/database/contract/number_attributes.proto b/java/com/android/dialer/calllog/database/contract/number_attributes.proto
index e24f393..2e93291 100644
--- a/java/com/android/dialer/calllog/database/contract/number_attributes.proto
+++ b/java/com/android/dialer/calllog/database/contract/number_attributes.proto
@@ -24,7 +24,7 @@
 import "java/com/android/dialer/logging/contact_source.proto";
 
 // Information related to the phone number of the call.
-// Next ID: 13
+// Next ID: 12
 message NumberAttributes {
   // The name (which may be a person's name or business name, but not a number)
   // formatted exactly as it should appear to the user. If the user's locale or
@@ -52,22 +52,19 @@
   // The number is a call to a business from nearby places lookup.
   optional bool is_business = 6;
 
-  // The number is a call to the voicemail inbox.
-  optional bool is_voicemail = 7;
-
   // Can the number be reported as invalid through People API
-  optional bool can_report_as_invalid_number = 8;
+  optional bool can_report_as_invalid_number = 7;
 
   // True if the CP2 information is incomplete and needs to be queried at
   // display time.
-  optional bool is_cp2_info_incomplete = 9;
+  optional bool is_cp2_info_incomplete = 8;
 
   // Whether the number is blocked.
-  optional bool is_blocked = 10;
+  optional bool is_blocked = 9;
 
   // Whether the number is spam.
-  optional bool is_spam = 11;
+  optional bool is_spam = 10;
 
   // Source of the contact associated with the number.
-  optional com.android.dialer.logging.ContactSource.Type contact_source = 12;
+  optional com.android.dialer.logging.ContactSource.Type contact_source = 11;
 }
\ No newline at end of file
diff --git a/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java b/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java
new file mode 100644
index 0000000..e8dc3e1
--- /dev/null
+++ b/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 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.calllog.datasources.voicemail;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
+import com.android.dialer.calllog.datasources.CallLogDataSource;
+import com.android.dialer.calllog.datasources.CallLogMutations;
+import com.android.dialer.calllog.datasources.util.RowCombiner;
+import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
+import com.android.dialer.compat.telephony.TelephonyManagerCompat;
+import com.android.dialer.telecom.TelecomUtil;
+import com.android.dialer.util.PermissionsUtil;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.util.List;
+import java.util.Map.Entry;
+import javax.inject.Inject;
+
+/** Provide information for whether the call is a call to the voicemail inbox. */
+public class VoicemailDataSource implements CallLogDataSource {
+
+  private final ListeningExecutorService backgroundExecutor;
+
+  @Inject
+  VoicemailDataSource(@BackgroundExecutor ListeningExecutorService backgroundExecutor) {
+    this.backgroundExecutor = backgroundExecutor;
+  }
+
+  @Override
+  public ListenableFuture<Boolean> isDirty(Context appContext) {
+    // The isVoicemail status is immutable and permanent. The call will always show as "Voicemail"
+    // even if the SIM is swapped. Dialing the row will result in some unexpected number after a SIM
+    // swap but this is deemed acceptable.
+    return Futures.immediateFuture(false);
+  }
+
+  @Override
+  @SuppressWarnings("missingPermission")
+  public ListenableFuture<Void> fill(Context appContext, CallLogMutations mutations) {
+    if (!PermissionsUtil.hasReadPhoneStatePermissions(appContext)) {
+      return Futures.immediateFuture(null);
+    }
+    return backgroundExecutor.submit(
+        () -> {
+          TelecomManager telecomManager = appContext.getSystemService(TelecomManager.class);
+          for (Entry<Long, ContentValues> insert : mutations.getInserts().entrySet()) {
+            ContentValues values = insert.getValue();
+            PhoneAccountHandle phoneAccountHandle =
+                TelecomUtil.composePhoneAccountHandle(
+                    values.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME),
+                    values.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_ID));
+            DialerPhoneNumber dialerPhoneNumber;
+            try {
+              dialerPhoneNumber =
+                  DialerPhoneNumber.parseFrom(values.getAsByteArray(AnnotatedCallLog.NUMBER));
+            } catch (InvalidProtocolBufferException e) {
+              throw new IllegalStateException(e);
+            }
+
+            if (telecomManager.isVoiceMailNumber(
+                phoneAccountHandle, dialerPhoneNumber.getNormalizedNumber())) {
+              values.put(AnnotatedCallLog.IS_VOICEMAIL_CALL, 1);
+              TelephonyManager telephonyManager =
+                  TelephonyManagerCompat.getTelephonyManagerForPhoneAccountHandle(
+                      appContext, phoneAccountHandle);
+              values.put(
+                  AnnotatedCallLog.VOICEMAIL_CALL_TAG, telephonyManager.getVoiceMailAlphaTag());
+            }
+          }
+          return null;
+        });
+  }
+
+  @Override
+  public ListenableFuture<Void> onSuccessfulFill(Context appContext) {
+    return Futures.immediateFuture(null);
+  }
+
+  @Override
+  public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) {
+    return new RowCombiner(individualRowsSortedByTimestampDesc)
+        .useMostRecentInt(AnnotatedCallLog.IS_VOICEMAIL_CALL)
+        .useMostRecentString(AnnotatedCallLog.VOICEMAIL_CALL_TAG)
+        .combine();
+  }
+
+  @Override
+  public void registerContentObservers(Context appContext) {}
+}
diff --git a/java/com/android/dialer/calllog/model/CoalescedRow.java b/java/com/android/dialer/calllog/model/CoalescedRow.java
index 2b6db97..737e736 100644
--- a/java/com/android/dialer/calllog/model/CoalescedRow.java
+++ b/java/com/android/dialer/calllog/model/CoalescedRow.java
@@ -39,6 +39,7 @@
         .setFeatures(0)
         .setCallType(0)
         .setNumberAttributes(NumberAttributes.getDefaultInstance())
+        .setIsVoicemailCall(false)
         .setCoalescedIds(CoalescedIds.getDefaultInstance());
   }
 
@@ -80,6 +81,11 @@
 
   public abstract NumberAttributes numberAttributes();
 
+  public abstract boolean isVoicemailCall();
+
+  @Nullable
+  public abstract String voicemailCallTag();
+
   public abstract CoalescedIds coalescedIds();
 
   /** Builder for {@link CoalescedRow}. */
@@ -117,6 +123,10 @@
 
     public abstract Builder setNumberAttributes(NumberAttributes numberAttributes);
 
+    public abstract Builder setIsVoicemailCall(boolean isVoicemail);
+
+    public abstract Builder setVoicemailCallTag(@Nullable String tag);
+
     public abstract Builder setCoalescedIds(CoalescedIds coalescedIds);
 
     public abstract CoalescedRow build();
diff --git a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
index 0b1c6c9..a5cfd3f 100644
--- a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
+++ b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
@@ -44,8 +44,10 @@
   private static final int PHONE_ACCOUNT_COLOR = 11;
   private static final int FEATURES = 12;
   private static final int NUMBER_ATTRIBUTES = 13;
-  private static final int CALL_TYPE = 14;
-  private static final int COALESCED_IDS = 15;
+  private static final int IS_VOICEMAIL_CALL = 14;
+  private static final int VOICEMAIL_CALL_TAG = 15;
+  private static final int CALL_TYPE = 16;
+  private static final int COALESCED_IDS = 17;
 
   CoalescedAnnotatedCallLogCursorLoader(Context context) {
     // CoalescedAnnotatedCallLog requires that PROJECTION be ALL_COLUMNS and the following params be
@@ -98,6 +100,8 @@
         .setFeatures(cursor.getInt(FEATURES))
         .setCallType(cursor.getInt(CALL_TYPE))
         .setNumberAttributes(numberAttributes)
+        .setIsVoicemailCall(cursor.getInt(IS_VOICEMAIL_CALL) == 1)
+        .setVoicemailCallTag(cursor.getString(VOICEMAIL_CALL_TAG))
         .setCoalescedIds(coalescedIds)
         .build();
   }
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
index 4c2d124..74be21b 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
@@ -153,7 +153,8 @@
 
   private void setPhoto(CoalescedRow row) {
     PhotoInfo.Builder photoInfoBuilder =
-        NumberAttributesConverter.toPhotoInfoBuilder(row.numberAttributes());
+        NumberAttributesConverter.toPhotoInfoBuilder(row.numberAttributes())
+            .setIsVoicemail(row.isVoicemailCall());
     if (!TextUtils.isEmpty(row.formattedNumber())) {
       photoInfoBuilder.setFormattedNumber(row.formattedNumber());
     }
diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java
index fd5f6a3..69b42e3 100644
--- a/java/com/android/dialer/calllog/ui/menu/Modules.java
+++ b/java/com/android/dialer/calllog/ui/menu/Modules.java
@@ -176,7 +176,8 @@
   private static PhotoInfo createPhotoInfoFromRow(CoalescedRow row) {
     PhotoInfo.Builder photoInfoBuilder =
         NumberAttributesConverter.toPhotoInfoBuilder(row.numberAttributes())
-            .setIsVideo((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO);
+            .setIsVideo((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO)
+            .setIsVoicemail(row.isVoicemailCall());
     if (!TextUtils.isEmpty(row.formattedNumber())) {
       photoInfoBuilder.setFormattedNumber(row.formattedNumber());
     }
diff --git a/java/com/android/dialer/calllogutils/CallLogEntryText.java b/java/com/android/dialer/calllogutils/CallLogEntryText.java
index ab851cb..6f1047c 100644
--- a/java/com/android/dialer/calllogutils/CallLogEntryText.java
+++ b/java/com/android/dialer/calllogutils/CallLogEntryText.java
@@ -48,6 +48,10 @@
       return presentationName.get();
     }
 
+    if (row.isVoicemailCall() && !TextUtils.isEmpty(row.voicemailCallTag())) {
+      return row.voicemailCallTag();
+    }
+
     // Otherwise prefer the name.
     if (!TextUtils.isEmpty(row.numberAttributes().getName())) {
       return row.numberAttributes().getName();
diff --git a/java/com/android/dialer/calllogutils/NumberAttributesConverter.java b/java/com/android/dialer/calllogutils/NumberAttributesConverter.java
index df6b680..24567e0 100644
--- a/java/com/android/dialer/calllogutils/NumberAttributesConverter.java
+++ b/java/com/android/dialer/calllogutils/NumberAttributesConverter.java
@@ -34,7 +34,6 @@
         .setLookupUri(numberAttributes.getLookupUri())
         .setIsBusiness(numberAttributes.getIsBusiness())
         .setIsSpam(numberAttributes.getIsSpam())
-        .setIsVoicemail(numberAttributes.getIsVoicemail())
         .setIsBlocked(numberAttributes.getIsBlocked());
   }
 
@@ -52,7 +51,6 @@
         .setLookupUri(phoneLookupInfoConsolidator.getLookupUri())
         .setNumberTypeLabel(phoneLookupInfoConsolidator.getNumberLabel())
         .setIsBusiness(phoneLookupInfoConsolidator.isBusiness())
-        .setIsVoicemail(phoneLookupInfoConsolidator.isVoicemail())
         .setIsBlocked(phoneLookupInfoConsolidator.isBlocked())
         .setIsSpam(phoneLookupInfoConsolidator.isSpam())
         .setCanReportAsInvalidNumber(phoneLookupInfoConsolidator.canReportAsInvalidNumber())
diff --git a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
index 6e86756..f9ffd04 100644
--- a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
+++ b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
@@ -267,15 +267,6 @@
 
   /**
    * The {@link PhoneLookupInfo} passed to the constructor is associated with a number. This method
-   * returns whether the number is a voicemail number.
-   */
-  public boolean isVoicemail() {
-    // TODO(twyen): implement
-    return false;
-  }
-
-  /**
-   * The {@link PhoneLookupInfo} passed to the constructor is associated with a number. This method
    * returns whether the number is blocked.
    */
   public boolean isBlocked() {