Merge "Implement VoicemailDataSource"
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() {